From 2e34f739034e138f85d66fde90b2e02abdbdf3b4 Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:27:45 +0200 Subject: [PATCH 1/7] deps: support 3.13, drop 3.8 --- .github/workflows/pipeline.yml | 14 +- README.rst | 2 +- poetry.lock | 610 ++++++++++++++++----------------- pyproject.toml | 8 +- 4 files changed, 308 insertions(+), 326 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 98d4d615..04e85983 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -31,20 +31,20 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] exclude: - - os: macos-latest - python-version: "3.9" - os: macos-latest python-version: "3.10" - os: macos-latest python-version: "3.11" - - os: windows-latest - python-version: "3.9" + - os: macos-latest + python-version: "3.12" - os: windows-latest python-version: "3.10" - os: windows-latest python-version: "3.11" + - os: windows-latest + python-version: "3.11" runs-on: ${{ matrix.os }} defaults: @@ -80,7 +80,7 @@ jobs: run: | poetry run pytest --junitxml=test-reports/report-${{ matrix.os }}-${{ matrix.python-version }}.xml -o junit_suite_name=suite-${{ matrix.os }}-${{ matrix.python-version }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: report-${{ matrix.os }}-${{ matrix.python-version }} @@ -99,7 +99,7 @@ jobs: steps: - name: Download Test Result Artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: test-reports diff --git a/README.rst b/README.rst index 289326d7..6fd6921b 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ aioslsk aioslsk is a Python library for the SoulSeek protocol built on top of asyncio. -Supported Python versions are currently 3.8 - 3.12 +Supported Python versions are currently 3.9 - 3.13 You can find the full documentation `here `_ diff --git a/poetry.lock b/poetry.lock index d1f5153b..cf0159f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -150,13 +150,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" +version = "0.7.16" +description = "A light, configurable Sphinx theme" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] @@ -170,9 +170,6 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} - [[package]] name = "appdirs" version = "1.4.4" @@ -242,7 +239,6 @@ files = [ ] [package.dependencies] -importlib-metadata = {version = ">1", markers = "python_version <= \"3.8\""} pydantic = ">=2.0,<3.0.0" pydantic-settings = ">=2.0,<3.0.0" Sphinx = ">=4.0" @@ -266,21 +262,18 @@ files = [ {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, ] -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] @@ -689,51 +682,43 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "libcst" -version = "1.1.0" -description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7, 3.8, 3.9, and 3.10 programs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "libcst-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:63f75656fd733dc20354c46253fde3cf155613e37643c3eaf6f8818e95b7a3d1"}, - {file = "libcst-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ae11eb1ea55a16dc0cdc61b41b29ac347da70fec14cc4381248e141ee2fbe6c"}, - {file = "libcst-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bc745d0c06420fe2644c28d6ddccea9474fb68a2135904043676deb4fa1e6bc"}, - {file = "libcst-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1f2da45f1c45634090fd8672c15e0159fdc46853336686959b2d093b6e10fa"}, - {file = "libcst-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:003e5e83a12eed23542c4ea20fdc8de830887cc03662432bb36f84f8c4841b81"}, - {file = "libcst-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:3ebbb9732ae3cc4ae7a0e97890bed0a57c11d6df28790c2b9c869f7da653c7c7"}, - {file = "libcst-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d68c34e3038d3d1d6324eb47744cbf13f2c65e1214cf49db6ff2a6603c1cd838"}, - {file = "libcst-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9dffa1795c2804d183efb01c0f1efd20a7831db6a21a0311edf90b4100d67436"}, - {file = "libcst-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc9b6ac36d7ec9db2f053014ea488086ca2ed9c322be104fbe2c71ca759da4bb"}, - {file = "libcst-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b7a38ec4c1c009ac39027d51558b52851fb9234669ba5ba62283185963a31c"}, - {file = "libcst-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5297a16e575be8173185e936b7765c89a3ca69d4ae217a4af161814a0f9745a7"}, - {file = "libcst-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:7ccaf53925f81118aeaadb068a911fac8abaff608817d7343da280616a5ca9c1"}, - {file = "libcst-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:75816647736f7e09c6120bdbf408456f99b248d6272277eed9a58cf50fb8bc7d"}, - {file = "libcst-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8f26250f87ca849a7303ed7a4fd6b2c7ac4dec16b7d7e68ca6a476d7c9bfcdb"}, - {file = "libcst-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d37326bd6f379c64190a28947a586b949de3a76be00176b0732c8ee87d67ebe"}, - {file = "libcst-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3d8cf974cfa2487b28f23f56c4bff90d550ef16505e58b0dca0493d5293784b"}, - {file = "libcst-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d1271403509b0a4ee6ff7917c2d33b5a015f44d1e208abb1da06ba93b2a378"}, - {file = "libcst-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bca1841693941fdd18371824bb19a9702d5784cd347cb8231317dbdc7062c5bc"}, - {file = "libcst-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f36f592e035ef84f312a12b75989dde6a5f6767fe99146cdae6a9ee9aff40dd0"}, - {file = "libcst-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f561c9a84eca18be92f4ad90aa9bd873111efbea995449301719a1a7805dbc5c"}, - {file = "libcst-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97fbc73c87e9040e148881041fd5ffa2a6ebf11f64b4ccb5b52e574b95df1a15"}, - {file = "libcst-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99fdc1929703fd9e7408aed2e03f58701c5280b05c8911753a8d8619f7dfdda5"}, - {file = "libcst-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bf69cbbab5016d938aac4d3ae70ba9ccb3f90363c588b3b97be434e6ba95403"}, - {file = "libcst-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fe41b33aa73635b1651f64633f429f7aa21f86d2db5748659a99d9b7b1ed2a90"}, - {file = "libcst-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73c086705ed34dbad16c62c9adca4249a556c1b022993d511da70ea85feaf669"}, - {file = "libcst-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a07ecfabbbb8b93209f952a365549e65e658831e9231649f4f4e4263cad24b1"}, - {file = "libcst-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c653d9121d6572d8b7f8abf20f88b0a41aab77ff5a6a36e5a0ec0f19af0072e8"}, - {file = "libcst-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f1cd308a4c2f71d5e4eec6ee693819933a03b78edb2e4cc5e3ad1afd5fb3f07"}, - {file = "libcst-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8afb6101b8b3c86c5f9cec6b90ab4da16c3c236fe7396f88e8b93542bb341f7c"}, - {file = "libcst-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:d22d1abfe49aa60fc61fa867e10875a9b3024ba5a801112f4d7ba42d8d53242e"}, - {file = "libcst-1.1.0.tar.gz", hash = "sha256:0acbacb9a170455701845b7e940e2d7b9519db35a86768d86330a0b0deae1086"}, +version = "1.4.0" +description = "A concrete syntax tree with AST-like properties for Python 3.0 through 3.12 programs." +optional = false +python-versions = ">=3.9" +files = [ + {file = "libcst-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:279b54568ea1f25add50ea4ba3d76d4f5835500c82f24d54daae4c5095b986aa"}, + {file = "libcst-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3401dae41fe24565387a65baee3887e31a44e3e58066b0250bc3f3ccf85b1b5a"}, + {file = "libcst-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1989fa12d3cd79118ebd29ebe2a6976d23d509b1a4226bc3d66fcb7cb50bd5d"}, + {file = "libcst-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addc6d585141a7677591868886f6bda0577529401a59d210aa8112114340e129"}, + {file = "libcst-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17d71001cb25e94cfe8c3d997095741a8c4aa7a6d234c0f972bc42818c88dfaf"}, + {file = "libcst-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:2d47de16d105e7dd5f4e01a428d9f4dc1e71efd74f79766daf54528ce37f23c3"}, + {file = "libcst-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6227562fc5c9c1efd15dfe90b0971ae254461b8b6b23c1b617139b6003de1c1"}, + {file = "libcst-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3399e6c95df89921511b44d8c5bf6a75bcbc2d51f1f6429763609ba005c10f6b"}, + {file = "libcst-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48601e3e590e2d6a7ab8c019cf3937c70511a78d778ab3333764531253acdb33"}, + {file = "libcst-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42797309bb725f0f000510d5463175ccd7155395f09b5e7723971b0007a976d"}, + {file = "libcst-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb4e42ea107a37bff7f9fdbee9532d39f9ea77b89caa5c5112b37057b12e0838"}, + {file = "libcst-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:9d0cc3c5a2a51fa7e1d579a828c0a2e46b2170024fd8b1a0691c8a52f3abb2d9"}, + {file = "libcst-1.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7ece51d935bc9bf60b528473d2e5cc67cbb88e2f8146297e40ee2c7d80be6f13"}, + {file = "libcst-1.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:81653dea1cdfa4c6520a7c5ffb95fa4d220cbd242e446c7a06d42d8636bfcbba"}, + {file = "libcst-1.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6abce0e66bba2babfadc20530fd3688f672d565674336595b4623cd800b91ef"}, + {file = "libcst-1.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da9d7dc83801aba3b8d911f82dc1a375db0d508318bad79d9fb245374afe068"}, + {file = "libcst-1.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c54aa66c86d8ece9c93156a2cf5ca512b0dce40142fe9e072c86af2bf892411"}, + {file = "libcst-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:62e2682ee1567b6a89c91853865372bf34f178bfd237853d84df2b87b446e654"}, + {file = "libcst-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8ecdba8934632b4dadacb666cd3816627a6ead831b806336972ccc4ba7ca0e9"}, + {file = "libcst-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8e54c777b8d27339b70f304d16fc8bc8674ef1bd34ed05ea874bf4921eb5a313"}, + {file = "libcst-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061d6855ef30efe38b8a292b7e5d57c8e820e71fc9ec9846678b60a934b53bbb"}, + {file = "libcst-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0abf627ee14903d05d0ad9b2c6865f1b21eb4081e2c7bea1033f85db2b8bae"}, + {file = "libcst-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d024f44059a853b4b852cfc04fec33e346659d851371e46fc8e7c19de24d3da9"}, + {file = "libcst-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3c6a8faab9da48c5b371557d0999b4ca51f4f2cbd37ee8c2c4df0ac01c781465"}, + {file = "libcst-1.4.0.tar.gz", hash = "sha256:449e0b16604f054fa7f27c3ffe86ea7ef6c409836fe68fe4e752a1894175db00"}, ] [package.dependencies] pyyaml = ">=5.2" -typing-extensions = ">=3.7.4.2" -typing-inspect = ">=0.4.0" [package.extras] -dev = ["Sphinx (>=5.1.1)", "black (==23.9.1)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==2.0.0.post1)", "flake8 (>=3.7.8,<5)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.2)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.16)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.18)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.2.0)", "usort (==1.0.7)"] +dev = ["Sphinx (>=5.1.1)", "black (==23.12.1)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==2.1.0)", "flake8 (==7.0.0)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.4)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<1.6)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.18)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.6.0)", "usort (==1.0.8.post1)"] [[package]] name = "lxml" @@ -1190,119 +1175,123 @@ files = [ [[package]] name = "pydantic" -version = "2.8.2" +version = "2.9.0" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, - {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, + {file = "pydantic-2.9.0-py3-none-any.whl", hash = "sha256:f66a7073abd93214a20c5f7b32d56843137a7a2e70d02111f3be287035c45370"}, + {file = "pydantic-2.9.0.tar.gz", hash = "sha256:c7a8a9fdf7d100afa49647eae340e2d23efa382466a8d177efcd1381e9be5598"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""} +pydantic-core = "2.23.2" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] +tzdata = {version = "*", markers = "python_version >= \"3.9\""} [package.extras] email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.23.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, - {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, - {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, - {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, - {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, - {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, - {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, - {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, - {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, - {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, - {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, - {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, - {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, - {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, - {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, - {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, - {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, - {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, - {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, - {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, - {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, - {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, - {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, - {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, - {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, - {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, - {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, - {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, - {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, - {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, - {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, - {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, - {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, + {file = "pydantic_core-2.23.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7d0324a35ab436c9d768753cbc3c47a865a2cbc0757066cb864747baa61f6ece"}, + {file = "pydantic_core-2.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:276ae78153a94b664e700ac362587c73b84399bd1145e135287513442e7dfbc7"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:964c7aa318da542cdcc60d4a648377ffe1a2ef0eb1e996026c7f74507b720a78"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1cf842265a3a820ebc6388b963ead065f5ce8f2068ac4e1c713ef77a67b71f7c"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae90b9e50fe1bd115b24785e962b51130340408156d34d67b5f8f3fa6540938e"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ae65fdfb8a841556b52935dfd4c3f79132dc5253b12c0061b96415208f4d622"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8aa40f6ca803f95b1c1c5aeaee6237b9e879e4dfb46ad713229a63651a95fb"}, + {file = "pydantic_core-2.23.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c53100c8ee5a1e102766abde2158077d8c374bee0639201f11d3032e3555dfbc"}, + {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6b9dd6aa03c812017411734e496c44fef29b43dba1e3dd1fa7361bbacfc1354"}, + {file = "pydantic_core-2.23.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b18cf68255a476b927910c6873d9ed00da692bb293c5b10b282bd48a0afe3ae2"}, + {file = "pydantic_core-2.23.2-cp310-none-win32.whl", hash = "sha256:e460475719721d59cd54a350c1f71c797c763212c836bf48585478c5514d2854"}, + {file = "pydantic_core-2.23.2-cp310-none-win_amd64.whl", hash = "sha256:5f3cf3721eaf8741cffaf092487f1ca80831202ce91672776b02b875580e174a"}, + {file = "pydantic_core-2.23.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ce8e26b86a91e305858e018afc7a6e932f17428b1eaa60154bd1f7ee888b5f8"}, + {file = "pydantic_core-2.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e9b24cca4037a561422bf5dc52b38d390fb61f7bfff64053ce1b72f6938e6b2"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753294d42fb072aa1775bfe1a2ba1012427376718fa4c72de52005a3d2a22178"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:257d6a410a0d8aeb50b4283dea39bb79b14303e0fab0f2b9d617701331ed1515"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8319e0bd6a7b45ad76166cc3d5d6a36c97d0c82a196f478c3ee5346566eebfd"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a05c0240f6c711eb381ac392de987ee974fa9336071fb697768dfdb151345ce"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d5b0ff3218858859910295df6953d7bafac3a48d5cd18f4e3ed9999efd2245f"}, + {file = "pydantic_core-2.23.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96ef39add33ff58cd4c112cbac076726b96b98bb8f1e7f7595288dcfb2f10b57"}, + {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0102e49ac7d2df3379ef8d658d3bc59d3d769b0bdb17da189b75efa861fc07b4"}, + {file = "pydantic_core-2.23.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6612c2a844043e4d10a8324c54cdff0042c558eef30bd705770793d70b224aa"}, + {file = "pydantic_core-2.23.2-cp311-none-win32.whl", hash = "sha256:caffda619099cfd4f63d48462f6aadbecee3ad9603b4b88b60cb821c1b258576"}, + {file = "pydantic_core-2.23.2-cp311-none-win_amd64.whl", hash = "sha256:6f80fba4af0cb1d2344869d56430e304a51396b70d46b91a55ed4959993c0589"}, + {file = "pydantic_core-2.23.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c83c64d05ffbbe12d4e8498ab72bdb05bcc1026340a4a597dc647a13c1605ec"}, + {file = "pydantic_core-2.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6294907eaaccf71c076abdd1c7954e272efa39bb043161b4b8aa1cd76a16ce43"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a801c5e1e13272e0909c520708122496647d1279d252c9e6e07dac216accc41"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc0c316fba3ce72ac3ab7902a888b9dc4979162d320823679da270c2d9ad0cad"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b06c5d4e8701ac2ba99a2ef835e4e1b187d41095a9c619c5b185c9068ed2a49"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82764c0bd697159fe9947ad59b6db6d7329e88505c8f98990eb07e84cc0a5d81"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b1a195efd347ede8bcf723e932300292eb13a9d2a3c1f84eb8f37cbbc905b7f"}, + {file = "pydantic_core-2.23.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7efb12e5071ad8d5b547487bdad489fbd4a5a35a0fc36a1941517a6ad7f23e0"}, + {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5dd0ec5f514ed40e49bf961d49cf1bc2c72e9b50f29a163b2cc9030c6742aa73"}, + {file = "pydantic_core-2.23.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:820f6ee5c06bc868335e3b6e42d7ef41f50dfb3ea32fbd523ab679d10d8741c0"}, + {file = "pydantic_core-2.23.2-cp312-none-win32.whl", hash = "sha256:3713dc093d5048bfaedbba7a8dbc53e74c44a140d45ede020dc347dda18daf3f"}, + {file = "pydantic_core-2.23.2-cp312-none-win_amd64.whl", hash = "sha256:e1895e949f8849bc2757c0dbac28422a04be031204df46a56ab34bcf98507342"}, + {file = "pydantic_core-2.23.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:da43cbe593e3c87d07108d0ebd73771dc414488f1f91ed2e204b0370b94b37ac"}, + {file = "pydantic_core-2.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:64d094ea1aa97c6ded4748d40886076a931a8bf6f61b6e43e4a1041769c39dd2"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084414ffe9a85a52940b49631321d636dadf3576c30259607b75516d131fecd0"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043ef8469f72609c4c3a5e06a07a1f713d53df4d53112c6d49207c0bd3c3bd9b"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3649bd3ae6a8ebea7dc381afb7f3c6db237fc7cebd05c8ac36ca8a4187b03b30"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6db09153d8438425e98cdc9a289c5fade04a5d2128faff8f227c459da21b9703"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5668b3173bb0b2e65020b60d83f5910a7224027232c9f5dc05a71a1deac9f960"}, + {file = "pydantic_core-2.23.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1c7b81beaf7c7ebde978377dc53679c6cba0e946426fc7ade54251dfe24a7604"}, + {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ae579143826c6f05a361d9546446c432a165ecf1c0b720bbfd81152645cb897d"}, + {file = "pydantic_core-2.23.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:19f1352fe4b248cae22a89268720fc74e83f008057a652894f08fa931e77dced"}, + {file = "pydantic_core-2.23.2-cp313-none-win32.whl", hash = "sha256:e1a79ad49f346aa1a2921f31e8dbbab4d64484823e813a002679eaa46cba39e1"}, + {file = "pydantic_core-2.23.2-cp313-none-win_amd64.whl", hash = "sha256:582871902e1902b3c8e9b2c347f32a792a07094110c1bca6c2ea89b90150caac"}, + {file = "pydantic_core-2.23.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:743e5811b0c377eb830150d675b0847a74a44d4ad5ab8845923d5b3a756d8100"}, + {file = "pydantic_core-2.23.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6650a7bbe17a2717167e3e23c186849bae5cef35d38949549f1c116031b2b3aa"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56e6a12ec8d7679f41b3750ffa426d22b44ef97be226a9bab00a03365f217b2b"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810ca06cca91de9107718dc83d9ac4d2e86efd6c02cba49a190abcaf33fb0472"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:785e7f517ebb9890813d31cb5d328fa5eda825bb205065cde760b3150e4de1f7"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef71ec876fcc4d3bbf2ae81961959e8d62f8d74a83d116668409c224012e3af"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d50ac34835c6a4a0d456b5db559b82047403c4317b3bc73b3455fefdbdc54b0a"}, + {file = "pydantic_core-2.23.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16b25a4a120a2bb7dab51b81e3d9f3cde4f9a4456566c403ed29ac81bf49744f"}, + {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:41ae8537ad371ec018e3c5da0eb3f3e40ee1011eb9be1da7f965357c4623c501"}, + {file = "pydantic_core-2.23.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07049ec9306ec64e955b2e7c40c8d77dd78ea89adb97a2013d0b6e055c5ee4c5"}, + {file = "pydantic_core-2.23.2-cp38-none-win32.whl", hash = "sha256:086c5db95157dc84c63ff9d96ebb8856f47ce113c86b61065a066f8efbe80acf"}, + {file = "pydantic_core-2.23.2-cp38-none-win_amd64.whl", hash = "sha256:67b6655311b00581914aba481729971b88bb8bc7996206590700a3ac85e457b8"}, + {file = "pydantic_core-2.23.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:358331e21a897151e54d58e08d0219acf98ebb14c567267a87e971f3d2a3be59"}, + {file = "pydantic_core-2.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c4d9f15ffe68bcd3898b0ad7233af01b15c57d91cd1667f8d868e0eacbfe3f87"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0123655fedacf035ab10c23450163c2f65a4174f2bb034b188240a6cf06bb123"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e6e3ccebdbd6e53474b0bb7ab8b88e83c0cfe91484b25e058e581348ee5a01a5"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc535cb898ef88333cf317777ecdfe0faac1c2a3187ef7eb061b6f7ecf7e6bae"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aab9e522efff3993a9e98ab14263d4e20211e62da088298089a03056980a3e69"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05b366fb8fe3d8683b11ac35fa08947d7b92be78ec64e3277d03bd7f9b7cda79"}, + {file = "pydantic_core-2.23.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7568f682c06f10f30ef643a1e8eec4afeecdafde5c4af1b574c6df079e96f96c"}, + {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cdd02a08205dc90238669f082747612cb3c82bd2c717adc60f9b9ecadb540f80"}, + {file = "pydantic_core-2.23.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a2ab4f410f4b886de53b6bddf5dd6f337915a29dd9f22f20f3099659536b2f6"}, + {file = "pydantic_core-2.23.2-cp39-none-win32.whl", hash = "sha256:0448b81c3dfcde439551bb04a9f41d7627f676b12701865c8a2574bcea034437"}, + {file = "pydantic_core-2.23.2-cp39-none-win_amd64.whl", hash = "sha256:4cebb9794f67266d65e7e4cbe5dcf063e29fc7b81c79dc9475bd476d9534150e"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e758d271ed0286d146cf7c04c539a5169a888dd0b57026be621547e756af55bc"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f477d26183e94eaafc60b983ab25af2a809a1b48ce4debb57b343f671b7a90b6"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da3131ef2b940b99106f29dfbc30d9505643f766704e14c5d5e504e6a480c35e"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329a721253c7e4cbd7aad4a377745fbcc0607f9d72a3cc2102dd40519be75ed2"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7706e15cdbf42f8fab1e6425247dfa98f4a6f8c63746c995d6a2017f78e619ae"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e64ffaf8f6e17ca15eb48344d86a7a741454526f3a3fa56bc493ad9d7ec63936"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dd59638025160056687d598b054b64a79183f8065eae0d3f5ca523cde9943940"}, + {file = "pydantic_core-2.23.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:12625e69b1199e94b0ae1c9a95d000484ce9f0182f9965a26572f054b1537e44"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5d813fd871b3d5c3005157622ee102e8908ad6011ec915a18bd8fde673c4360e"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1eb37f7d6a8001c0f86dc8ff2ee8d08291a536d76e49e78cda8587bb54d8b329"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce7eaf9a98680b4312b7cebcdd9352531c43db00fca586115845df388f3c465"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f087879f1ffde024dd2788a30d55acd67959dcf6c431e9d3682d1c491a0eb474"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ce883906810b4c3bd90e0ada1f9e808d9ecf1c5f0b60c6b8831d6100bcc7dd6"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a8031074a397a5925d06b590121f8339d34a5a74cfe6970f8a1124eb8b83f4ac"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23af245b8f2f4ee9e2c99cb3f93d0e22fb5c16df3f2f643f5a8da5caff12a653"}, + {file = "pydantic_core-2.23.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c57e493a0faea1e4c38f860d6862ba6832723396c884fbf938ff5e9b224200e2"}, + {file = "pydantic_core-2.23.2.tar.gz", hash = "sha256:95d6bf449a1ac81de562d65d180af5d8c19672793c81877a2eda8fde5d08f2fd"}, ] [package.dependencies] @@ -1469,17 +1458,6 @@ files = [ [package.extras] cli = ["click (>=5.0)"] -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -1576,38 +1554,39 @@ files = [ [[package]] name = "sphinx" -version = "7.1.2" +version = "7.4.7" description = "Python documentation generator" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, - {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.13" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" +importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-rtd-theme" @@ -1630,47 +1609,50 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.4" +version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.1" +version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] @@ -1703,32 +1685,34 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] @@ -1779,20 +1763,16 @@ files = [ ] [[package]] -name = "typing-inspect" -version = "0.9.0" -description = "Runtime inspection utilities for typing module." +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" optional = false -python-versions = "*" +python-versions = ">=2" files = [ - {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, - {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] -[package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - [[package]] name = "urllib3" version = "2.2.2" @@ -1812,13 +1792,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "voluptuous" -version = "0.14.2" +version = "0.15.2" description = "Python data validation library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "voluptuous-0.14.2-py3-none-any.whl", hash = "sha256:efc1dadc9ae32a30cc622602c1400a17b7bf8ee2770d64f70418144860739c3b"}, - {file = "voluptuous-0.14.2.tar.gz", hash = "sha256:533e36175967a310f1b73170d091232bf881403e4ebe52a9b4ade8404d151f5d"}, + {file = "voluptuous-0.15.2-py3-none-any.whl", hash = "sha256:016348bc7788a9af9520b1764ebd4de0df41fe2138ebe9e06fa036bf86a65566"}, + {file = "voluptuous-0.15.2.tar.gz", hash = "sha256:6ffcab32c4d3230b4d2af3a577c87e1908a714a11f6f95570456b1849b0279aa"}, ] [[package]] @@ -1885,101 +1865,103 @@ test = ["gevent (>=20.6.2)"] [[package]] name = "yarl" -version = "1.9.4" +version = "1.10.0" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.10.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1718c0bca5a61edac7a57dcc11856cb01bde13a9360a3cb6baf384b89cfc0b40"}, + {file = "yarl-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4657fd290d556a5f3018d07c7b7deadcb622760c0125277d10a11471c340054"}, + {file = "yarl-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:044b76d069e69c6b0246f071ebac0576f89c772f806d66ef51e662bd015d03c7"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5527d32506c11150ca87f33820057dc284e2a01a87f0238555cada247a8b278"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36d12d78b8b0d46099d413c8689b5510ad9ce5e443363d1c37b6ac5b3d7cbdfb"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11f7f8a72b3e26c533fa7ffa7a8068f4e3aad7b67c5cf7b17ea8c79fc81d9830"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88173836a25b7e5dce989eeee3b92d8ef5cdf512830d4155c6212de98e616f70"}, + {file = "yarl-1.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c382e189af10070bcb39caa9406b9cc47b26c1d2257979f11fe03a38be09fea9"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:534b8bc181dca1691cf491c263e084af678a8fb6b6181687c788027d8c317026"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f3372f9ae1d1f001826b77d0b29d4220e84f6c5f53915e71a825cdd02600065"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cca9ba00be4bb8a051c4007b60fc91d6c9728c8b70c86cee4c24be9d641002f"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a9d8c4be5658834dc688072239d220631ad4b71ff79a5f3d17fb653f16d10759"}, + {file = "yarl-1.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff45a655ca51e1cb778abbb586083fddb7d896332f47bb3b03bc75e30c25649f"}, + {file = "yarl-1.10.0-cp310-cp310-win32.whl", hash = "sha256:9ef7ce61958b3c7b2e2e0927c52d35cf367c5ee410e06e1337ecc83a90c23b95"}, + {file = "yarl-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:48a48261f8d610b0e15fed033e74798763bc2f8f2c0d769a2a0732511af71f1e"}, + {file = "yarl-1.10.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:308d1cce071b5b500e3d95636bbf15dfdb8e87ed081b893555658a7f9869a156"}, + {file = "yarl-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc66927f6362ed613a483c22618f88f014994ccbd0b7a25ec1ebc8c472d4b40a"}, + {file = "yarl-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c4d13071c5b99974cfe2f94c749ecc4baf882f7c4b6e4c40ca3d15d1b7e81f24"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:348ad53acd41caa489df7db352d620c982ab069855d9635dda73d685bbbc3636"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:293f7c2b30d015de3f1441c4ee764963b86636fde881b4d6093498d1e8711f69"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:315e8853d0ea46aabdce01f1f248fff7b9743de89b555c5f0487f54ac84beae8"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:012c506b2c23be4500fb97509aa7e6a575996fb317b80667fa26899d456e2aaf"}, + {file = "yarl-1.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f769c2708c31227c5349c3e4c668c8b4b2e25af3e7263723f2ef33e8e3906a0"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f6ac063a4e9bbd4f6cc88cc621516a44d6aec66862ea8399ba063374e4b12c7"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:18b7ce6d8c35da8e16dcc8de124a80e250fc8c73f8c02663acf2485c874f1972"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b80246bdee036381636e73ef0f19b032912064622b0e5ee44f6960fd11df12aa"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:183dd37bb5471e8017ab8a998c1ea070b4a0b08a97a7c4e20e0c7ccbe8ebb999"}, + {file = "yarl-1.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9b6d0d7522b514f054b359409817af4c5ed76fa4fe42d8bd1ed12956804cf595"}, + {file = "yarl-1.10.0-cp311-cp311-win32.whl", hash = "sha256:6026a6ef14d038a38ca9d81422db4b6bb7d5da94f9d08f21e0ad9ebd9c4bc3bb"}, + {file = "yarl-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:190e70d2f9f16f1c9d666c103d635c9ed4bf8de7803e9fa0495eec405a3e96a8"}, + {file = "yarl-1.10.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6bc602c7413e1b5223bc988947125998cb54d6184de45a871985daacc23e6c8c"}, + {file = "yarl-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bf733c835ebbd52bd78a52b919205e0f06d8571f71976a0259e5bcc20d0a2f44"}, + {file = "yarl-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e91ed5f6818e1e3806eaeb7b14d9e17b90340f23089451ea59a89a29499d760"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23057a004bc9735008eb2a04b6ce94c6c06219cdf2b193997fd3ae6039eb3196"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b922c32a1cff62bc43d408d1a8745abeed0a705793f2253c622bf3521922198"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be199fed28861d72df917e355287ad6835555d8210e7f8203060561f24d7d842"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cece693380c1c4a606cdcaa0c54eda8f72cfe1ba83f5149b9023bb955e8fa8e"}, + {file = "yarl-1.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff8e803d8ca170e632fb3b4df1bfd29ba29be8edc3e9306c5ffa5fadea234a4f"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:30dde3a8b88c80a4f049eb4dd240d2a02e89174da6be2525541f949bf9fa38ab"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dff84623e7098cf9bfbb5187f9883051af652b0ce08b9f7084cc8630b87b6457"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e69b55965a47dd6c79e578abd7d85637b1bb4a7565436630826bdb28aa9b7ad"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5d0c9e1dcc92d46ca89608fe4763fc2362f1e81c19a922c67dbc0f20951466e4"}, + {file = "yarl-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32e79d5ae975f7c2cc29f7104691fc9be5ee3724f24e1a7254d72f6219672108"}, + {file = "yarl-1.10.0-cp312-cp312-win32.whl", hash = "sha256:762a196612c2aba4197cd271da65fe08308f7ddf130dc63842c7a76d774b6a2c"}, + {file = "yarl-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:8c6214071f653d21bb7b43f7ee519afcbf7084263bb43408f4939d14558290db"}, + {file = "yarl-1.10.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0e0aea8319fdc1ac340236e58b0b7dc763621bce6ce98124a9d58104cafd0aaa"}, + {file = "yarl-1.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b3bf343b4ef9ec600d75363eb9b48ab3bd53b53d4e1c5a9fbf0cfe7ba73a47f"}, + {file = "yarl-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:05b07e6e0f715eaae9d927a302d9220724392f3c0b4e7f8dfa174bf2e1b8433e"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7bd531d7eec4aa7ef8a99fef91962eeea5158a53af0ec507c476ddf8ebc29c"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:183136dc5d5411872e7529c924189a2e26fac5a7f9769cf13ef854d1d653ad36"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c77a3c10af4aaf8891578fe492ef0990c65cf7005dd371f5ea8007b420958bf6"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:030d41d48217b180c5a176e59c49d212d54d89f6f53640fa4c1a1766492aec27"}, + {file = "yarl-1.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4f43ba30d604ba391bc7fe2dd104d6b87b62b0de4bbde79e362524b8a1eb75"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:637dd0f55d1781d4634c23994101c509e455b5ab61af9086b5763b7eca9359aa"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:99e7459ee86a3b81e57777afd3825b8b1acaac8a99f9c0bd02415d80eb3c371b"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a80cdb3c15c15b33ecdb080546dcb022789b0084ca66ad41ffa0fe09857fca11"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1824bfb932d8100e5c94f4f98c078f23ebc6f6fa93acc3d95408762089c54a06"}, + {file = "yarl-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:90fd64ce00f594db02f603efa502521c440fa1afcf6266be82eb31f19d2d9561"}, + {file = "yarl-1.10.0-cp313-cp313-win32.whl", hash = "sha256:687131ee4d045f3d58128ca28f5047ec902f7760545c39bbe003cc737c5a02b5"}, + {file = "yarl-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:493ad061ee025c5ed3a60893cd70204eead1b3f60ccc90682e752f95b845bd46"}, + {file = "yarl-1.10.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cd65588273d19f8483bc8f32a6fcf602e94a9a7ba287a1725977bd9527cd6c0c"}, + {file = "yarl-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6f64f8681671624f539eea5564518bc924524c25eb90ab24a7eddc2d872e668e"}, + {file = "yarl-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3576ed2c51f8525d4ff5c3279247aacff9540bb43b292c4a37a8e6c6e1691adb"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca42a9281807fdf8fba86e671d8fdd76f92e9302a6d332957f2bae51c774f8a7"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54a4b5e6a060d46cad6a3cf340f4cb268e6fbc89c589d82a2da58f7db47c47c8"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eec21d8c3aa932c5a89480b58fa877e9c48092ab838ccc76788cbc917ceec0d"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:273baee8a8af5989d5aab51c740e65bc2b1fc6619b9dd192cd16a3fae51100be"}, + {file = "yarl-1.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1bf63ba496cd4f12d30e916d9a52daa6c91433fedd9cd0d99fef3e13232836f"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f8e24b9a4afdffab399191a9f0b0e80eabc7b7fdb9f2dbccdeb8e4d28e5c57bb"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4c46454fafa31f7241083a0dd21814f63e0fcb4ae49662dc7e286fd6a5160ea1"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:beda87b63c08fb4df8cc5353eeefe68efe12aa4f5284958bd1466b14c85e508e"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:9a8d6a0e2b5617b5c15c59db25f20ba429f1fea810f2c09fbf93067cb21ab085"}, + {file = "yarl-1.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b453b3dbc1ed4c2907632d05b378123f3fb411cad05d8d96de7d95104ef11c70"}, + {file = "yarl-1.10.0-cp38-cp38-win32.whl", hash = "sha256:1ea30675fbf0ad6795c100da677ef6a8960a7db05ac5293f02a23c2230203c89"}, + {file = "yarl-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:347011ad09a8f9be3d41fe2d7d611c3a4de4d49aa77bcb9a8c03c7a82fc45248"}, + {file = "yarl-1.10.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:18bc4600eed1907762c1816bb16ac63bc52912e53b5e9a353eb0935a78e95496"}, + {file = "yarl-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeb6a40c5ae2616fd38c1e039c6dd50031bbfbc2acacfd7b70a5d64fafc70901"}, + {file = "yarl-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc544248b5263e1c0f61332ccf35e37404b54213f77ed17457f857f40af51452"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3352c69dc235850d6bf8ddad915931f00dcab208ac4248b9af46175204c2f5f9"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af5b52bfbbd5eb208cf1afe23c5ada443929e9b9d79e9fbc66cacc07e4e39748"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eafa7317063de4bc310716cdd9026c13f00b1629e649079a6908c3aafdf5046"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a162cf04fd1e8d81025ec651d14cac4f6e0ca73a3c0a9482de8691b944e3098a"}, + {file = "yarl-1.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:179b1df5e9cd99234ea65e63d5bfc6dd524b2c3b6cf68a14b94ccbe01ab37ddd"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:32d2e46848dea122484317485129f080220aa84aeb6a9572ad9015107cebeb07"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aa1aeb99408be0ca774c5126977eb085fedda6dd7d9198ce4ceb2d06a44325c7"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d2366e2f987f69752f0588d2035321aaf24272693d75f7f6bb7e8a0f48f7ccdd"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e8da33665ecc64cd3e593098adb449f9c65b4e3bc6338e75ad592da15453d898"}, + {file = "yarl-1.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b46c603bee1f2dd407b8358c2afc9b0472a22ccca528f114e1f4cd30dfecd22"}, + {file = "yarl-1.10.0-cp39-cp39-win32.whl", hash = "sha256:96422a3322b4d954f4c52403a2fc129ad118c151ee60a717847fb46a8480d1e1"}, + {file = "yarl-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:52d1ae09b0764017e330bb5bf9af760c0168c564225085bb806f687bccffda8a"}, + {file = "yarl-1.10.0-py3-none-any.whl", hash = "sha256:99eaa7d53f509ba1c2fea8fdfec15ba3cd36caca31d57ec6665073b148b5f260"}, + {file = "yarl-1.10.0.tar.gz", hash = "sha256:3bf10a395adac62177ba8ea738617e8de6cbb1cea6aa5d5dd2accde704fc8195"}, ] [package.dependencies] @@ -2007,5 +1989,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" -python-versions = ">=3.8,<3.13" -content-hash = "47fb13c3c7e57fb6548c2db004679527ad888fae13779334b9094654ce5eaa09" +python-versions = ">=3.9,<3.14" +content-hash = "dc53b347d85ebc1baeda7b5df457c4218fd2f2aa46f9e06fea6d063486c6a9d8" diff --git a/pyproject.toml b/pyproject.toml index 0fef369e..6c9eb4c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,16 +11,16 @@ packages = [{include = "aioslsk", from = "src"}] keywords = ["soulseek", "p2p", "async"] classifiers = [ "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Communications :: File Sharing" ] [tool.poetry.dependencies] -python = ">=3.8,<3.13" +python = ">=3.9,<3.14" mutagen = "^1.47.0" aiofiles = ">=22.1,<25.0" async-upnp-client = ">=0.33,<0.41" @@ -31,7 +31,7 @@ typing-extensions = "^4.12.2" multidict = "^6.0.5" [tool.poetry.group.dev.dependencies] -flake8 = { version = ">=6,<8", python = ">=3.8.1,<3.13" } +flake8 = { version = ">=6,<8", python = ">=3.9.1,<3.14" } mypy = "^1.11.2" no-implicit-optional = "^1.4" pytest = ">=7.4.2,<9.0.0" @@ -40,7 +40,7 @@ pytest-unordered = ">=0.5.2,<0.7.0" pytest-asyncio = ">=0.21.1,<0.25.0" sphinx = "^7.0.0" sphinx-rtd-theme = "^2.0.0" -autodoc-pydantic = { version = "^2.2.0", python = ">=3.8.1,<3.13" } +autodoc-pydantic = { version = "^2.2.0", python = ">=3.9.1,<3.14" } [tool.poetry.group.tools] optional = true From de7df8613677bc2a02d9b45a7b5d480d2f45872e Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:38:25 +0200 Subject: [PATCH 2/7] deps: change to RC2 --- .github/workflows/pipeline.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 04e85983..e5f3746b 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -10,8 +10,8 @@ jobs: linting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 - uses: snok/install-poetry@v1 - name: Install dependencies run: | @@ -31,7 +31,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13.0rc2"] exclude: - os: macos-latest python-version: "3.10" @@ -44,7 +44,7 @@ jobs: - os: windows-latest python-version: "3.11" - os: windows-latest - python-version: "3.11" + python-version: "3.12" runs-on: ${{ matrix.os }} defaults: @@ -52,10 +52,10 @@ jobs: shell: bash steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -67,7 +67,7 @@ jobs: - name: Load cached venv id: cached-pip-wheels - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache key: venv-${{ runner.os }}-${{ matrix.python-version }} From b8c79267c7982771b8e9705837f2eb9f3dbd7a2f Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sun, 8 Sep 2024 15:39:52 +0200 Subject: [PATCH 3/7] deps: change to RC1 --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index e5f3746b..64f621ab 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -31,7 +31,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13.0rc2"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13.0-rc.1"] exclude: - os: macos-latest python-version: "3.10" From ae8025a0485dde3f3b2c0284e968f73e2fcd6e61 Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sun, 8 Sep 2024 18:10:11 +0200 Subject: [PATCH 4/7] deps: remove backward compatibility for typing in 3.8 --- docs/source/USAGE.rst | 8 +- src/aioslsk/base_manager.py | 3 +- src/aioslsk/client.py | 4 +- src/aioslsk/commands.py | 44 ++++++----- src/aioslsk/constants.py | 3 +- src/aioslsk/distributed.py | 16 ++-- src/aioslsk/events.py | 61 ++++++--------- src/aioslsk/log_utils.py | 9 ++- src/aioslsk/naming.py | 11 ++- src/aioslsk/network/connection.py | 15 +--- src/aioslsk/network/network.py | 52 ++++++------- src/aioslsk/network/upnp.py | 13 ++-- src/aioslsk/protocol/messages.py | 88 +++++++++++----------- src/aioslsk/protocol/primitives.py | 40 +++++----- src/aioslsk/room/manager.py | 15 ++-- src/aioslsk/room/model.py | 10 +-- src/aioslsk/search/manager.py | 12 +-- src/aioslsk/search/model.py | 15 ++-- src/aioslsk/settings.py | 22 +++--- src/aioslsk/shares/cache.py | 16 ++-- src/aioslsk/shares/manager.py | 53 ++++++------- src/aioslsk/shares/model.py | 12 +-- src/aioslsk/shares/utils.py | 6 +- src/aioslsk/tasks.py | 3 +- src/aioslsk/transfer/cache.py | 14 ++-- src/aioslsk/transfer/manager.py | 34 ++++----- src/aioslsk/transfer/model.py | 10 +-- src/aioslsk/user/manager.py | 10 +-- src/aioslsk/utils.py | 8 +- tests/e2e/fixtures.py | 6 +- tests/e2e/mock/constants.py | 3 +- tests/e2e/mock/distributed.py | 41 +++++----- tests/e2e/mock/model.py | 18 ++--- tests/e2e/mock/server.py | 64 ++++++++-------- tests/e2e/test_e2e_distributed.py | 11 ++- tests/unit/network/test_network.py | 6 +- tests/unit/shares/test_shares_manager.py | 16 ++-- tests/unit/transfer/test_transfer_cache.py | 3 +- tests/unit/transfer/test_transfer_state.py | 13 ++-- 39 files changed, 373 insertions(+), 415 deletions(-) diff --git a/docs/source/USAGE.rst b/docs/source/USAGE.rst index ef1067db..f1772f21 100644 --- a/docs/source/USAGE.rst +++ b/docs/source/USAGE.rst @@ -242,9 +242,9 @@ A couple of methods are available to retrieve transfers: from aioslsk.transfer.model import Transfer - all_transfers = List[Transfer] = client.transfers.transfers - downloads: List[Transfer] = client.transfers.get_downloads() - uploads: List[Transfer] = client.transfers.get_uploads() + all_transfers = list[Transfer] = client.transfers.transfers + downloads: list[Transfer] = client.transfers.get_downloads() + uploads: list[Transfer] = client.transfers.get_uploads() Events are available to listen for the transfer progress: @@ -555,7 +555,7 @@ Example a strategy that places files in a directory containing the current date: class DatetimeDirectoryStrategy(NamingStrategy): # Override the apply method - def apply(self, remote_path: str, local_dir: str, local_filename: str) -> Tuple[str, str]: + def apply(self, remote_path: str, local_dir: str, local_filename: str) -> tuple[str, str]: current_datetime = datetime.now().strftime('%Y-%M-%d') return os.path.join(local_dir, current_datetime), local_filename diff --git a/src/aioslsk/base_manager.py b/src/aioslsk/base_manager.py index ee82b5dd..8517133d 100644 --- a/src/aioslsk/base_manager.py +++ b/src/aioslsk/base_manager.py @@ -1,6 +1,5 @@ from abc import ABC import asyncio -from typing import List class BaseManager(ABC): @@ -23,7 +22,7 @@ async def start(self): loading the data but before connecting """ - async def stop(self) -> List[asyncio.Task]: + async def stop(self) -> list[asyncio.Task]: """Cancel all running tasks. The implementation of this method should simply cancel the task and return, the code calling this method should be responsible for awaiting the cancellation diff --git a/src/aioslsk/client.py b/src/aioslsk/client.py index d71ffcd8..e7a2225c 100644 --- a/src/aioslsk/client.py +++ b/src/aioslsk/client.py @@ -2,7 +2,7 @@ import asyncio from async_timeout import timeout as atimeout import logging -from typing import List, Optional +from typing import Optional from .base_manager import BaseManager from .commands import BaseCommand, RC, RT @@ -81,7 +81,7 @@ def __init__( self.searches: SearchManager = self.create_search_manager() self.server_manager: ServerManager = self.create_server_manager() - self.services: List[BaseManager] = [ + self.services: list[BaseManager] = [ self.users, self.rooms, self.interests, diff --git a/src/aioslsk/commands.py b/src/aioslsk/commands.py index 5c6307cc..dcf217f8 100644 --- a/src/aioslsk/commands.py +++ b/src/aioslsk/commands.py @@ -3,11 +3,9 @@ import time from typing import ( Generic, - List, NamedTuple, Optional, Union, - Tuple, TypeVar, TYPE_CHECKING, ) @@ -77,9 +75,9 @@ RT = TypeVar('RT') """Response value type""" -Recommendations = Tuple[List[Recommendation], List[Recommendation]] -UserInterests = Tuple[List[str], List[str]] -SharesReply = Tuple[List[DirectoryData], List[DirectoryData]] +Recommendations = tuple[list[Recommendation], list[Recommendation]] +UserInterests = tuple[list[str], list[str]] +SharesReply = tuple[list[DirectoryData], list[DirectoryData]] class UserStatusInfo(NamedTuple): @@ -171,7 +169,7 @@ def handle_response(self, client: SoulSeekClient, response: GetUserStats.Respons ) -class GetRoomListCommand(BaseCommand[RoomList.Response, List[Room]]): +class GetRoomListCommand(BaseCommand[RoomList.Response, list[Room]]): async def send(self, client: SoulSeekClient): await client.network.send_server_messages( @@ -184,7 +182,7 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe RoomList.Response ) - def handle_response(self, client: SoulSeekClient, response: RoomList.Response) -> List[Room]: + def handle_response(self, client: SoulSeekClient, response: RoomList.Response) -> list[Room]: return [ room for name, room in client.rooms.rooms.items() if name in response.rooms @@ -238,7 +236,7 @@ def handle_response(self, client: SoulSeekClient, response: LeaveRoom.Response) return client.rooms.get_or_create_room(response.room) -class GrantRoomMembershipCommand(BaseCommand[PrivateRoomGrantMembership.Response, Tuple[Room, User]]): +class GrantRoomMembershipCommand(BaseCommand[PrivateRoomGrantMembership.Response, tuple[Room, User]]): def __init__(self, room: str, username: str): self.room: str = room @@ -260,14 +258,14 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe ) def handle_response( - self, client: SoulSeekClient, response: PrivateRoomGrantMembership.Response) -> Tuple[Room, User]: + self, client: SoulSeekClient, response: PrivateRoomGrantMembership.Response) -> tuple[Room, User]: return ( client.rooms.get_or_create_room(response.room), client.users.get_user_object(response.username) ) -class RevokeRoomMembershipCommand(BaseCommand[PrivateRoomRevokeMembership.Response, Tuple[Room, User]]): +class RevokeRoomMembershipCommand(BaseCommand[PrivateRoomRevokeMembership.Response, tuple[Room, User]]): def __init__(self, room: str, username: str): self.room: str = room @@ -289,7 +287,7 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe ) def handle_response( - self, client: SoulSeekClient, response: PrivateRoomRevokeMembership.Response) -> Tuple[Room, User]: + self, client: SoulSeekClient, response: PrivateRoomRevokeMembership.Response) -> tuple[Room, User]: return ( client.rooms.get_or_create_room(response.room), client.users.get_user_object(response.username) @@ -329,7 +327,7 @@ async def send(self, client: SoulSeekClient): ) -class GetItemRecommendationsCommand(BaseCommand[GetItemRecommendations.Response, List[Recommendation]]): +class GetItemRecommendationsCommand(BaseCommand[GetItemRecommendations.Response, list[Recommendation]]): def __init__(self, item: str): self.item: str = item @@ -349,7 +347,7 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe ) def handle_response( - self, client: SoulSeekClient, response: GetItemRecommendations.Response) -> List[Recommendation]: + self, client: SoulSeekClient, response: GetItemRecommendations.Response) -> list[Recommendation]: return response.recommendations @@ -387,7 +385,7 @@ def handle_response(self, client: SoulSeekClient, response: GetGlobalRecommendat return response.recommendations, response.unrecommendations -class GetItemSimilarUsersCommand(BaseCommand[GetItemSimilarUsers.Response, List[User]]): +class GetItemSimilarUsersCommand(BaseCommand[GetItemSimilarUsers.Response, list[User]]): def __init__(self, item: str): self.item: str = item @@ -406,11 +404,11 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe } ) - def handle_response(self, client: SoulSeekClient, response: GetItemSimilarUsers.Response) -> List[User]: + def handle_response(self, client: SoulSeekClient, response: GetItemSimilarUsers.Response) -> list[User]: return list(map(client.users.get_user_object, response.usernames)) -class GetSimilarUsersCommand(BaseCommand[GetSimilarUsers.Response, List[Tuple[User, int]]]): +class GetSimilarUsersCommand(BaseCommand[GetSimilarUsers.Response, list[tuple[User, int]]]): async def send(self, client: SoulSeekClient): await client.network.send_server_messages( @@ -423,7 +421,7 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe GetSimilarUsers.Response ) - def handle_response(self, client: SoulSeekClient, response: GetSimilarUsers.Response) -> List[Tuple[User, int]]: + def handle_response(self, client: SoulSeekClient, response: GetSimilarUsers.Response) -> list[tuple[User, int]]: similar_users = [] for similar_user in response.users: similar_users.append( @@ -436,7 +434,7 @@ def handle_response(self, client: SoulSeekClient, response: GetSimilarUsers.Resp return similar_users -class GetPeerAddressCommand(BaseCommand[GetPeerAddress.Response, Tuple[str, int, Optional[int]]]): +class GetPeerAddressCommand(BaseCommand[GetPeerAddress.Response, tuple[str, int, Optional[int]]]): def __init__(self, username: str): self.username: str = username @@ -456,7 +454,7 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe ) def handle_response( - self, client: SoulSeekClient, response: GetPeerAddress.Response) -> Tuple[str, int, Optional[int]]: + self, client: SoulSeekClient, response: GetPeerAddress.Response) -> tuple[str, int, Optional[int]]: return (response.ip, response.port, response.obfuscated_port) @@ -633,8 +631,8 @@ async def send(self, client: SoulSeekClient): class PrivateMessageUsersCommand(BaseCommand[None, None]): """Sends a private message to multiple users""" - def __init__(self, usernames: List[str], message: str): - self.usernames: List[str] = usernames + def __init__(self, usernames: list[str], message: str): + self.usernames: list[str] = usernames self.message: str = message async def send(self, client: SoulSeekClient): @@ -926,7 +924,7 @@ def handle_response( return response.directories, locked_dirs -class PeerGetDirectoryContentCommand(BaseCommand[PeerDirectoryContentsReply.Request, List[DirectoryData]]): +class PeerGetDirectoryContentCommand(BaseCommand[PeerDirectoryContentsReply.Request, list[DirectoryData]]): def __init__(self, username: str, directory: str): self.username: str = username @@ -951,5 +949,5 @@ def build_expected_response(self, client: SoulSeekClient) -> Optional[ExpectedRe ) def handle_response( - self, client: SoulSeekClient, response: PeerDirectoryContentsReply.Request) -> List[DirectoryData]: + self, client: SoulSeekClient, response: PeerDirectoryContentsReply.Request) -> list[DirectoryData]: return response.directories diff --git a/src/aioslsk/constants.py b/src/aioslsk/constants.py index c83899d1..c0f2e570 100644 --- a/src/aioslsk/constants.py +++ b/src/aioslsk/constants.py @@ -1,5 +1,4 @@ import re -from typing import List DEFAULT_LISTENING_HOST: str = '0.0.0.0' @@ -27,7 +26,7 @@ UPNP_DEFAULT_CHECK_INTERVAL: int = 600 UPNP_DEFAULT_LEASE_DURATION: int = 6 * 60 * 60 UPNP_DEFAULT_SEARCH_TIMEOUT: int = 10 -UPNP_MAPPING_SERVICES: List[str] = ["WANIPC", "WANPPP"] +UPNP_MAPPING_SERVICES: list[str] = ["WANIPC", "WANPPP"] POTENTIAL_PARENTS_CACHE_SIZE: int = 20 """Maximum amount of potential parents stored""" DEFAULT_COMMAND_TIMEOUT: float = 10 diff --git a/src/aioslsk/distributed.py b/src/aioslsk/distributed.py index fd83cd03..0dfb004b 100644 --- a/src/aioslsk/distributed.py +++ b/src/aioslsk/distributed.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from functools import partial import logging -from typing import Deque, List, Optional, Union, Tuple +from typing import Optional, Union from .base_manager import BaseManager from .constants import ( @@ -86,10 +86,10 @@ def __init__(self, settings: Settings, event_bus: EventBus, network: Network): """Distributed parent. This variable is `None` if we are looking for a parent """ - self.children: List[DistributedPeer] = [] - self.potential_parents: Deque[str] = deque( + self.children: list[DistributedPeer] = [] + self.potential_parents: deque[str] = deque( maxlen=POTENTIAL_PARENTS_CACHE_SIZE) - self.distributed_peers: List[DistributedPeer] = [] + self.distributed_peers: list[DistributedPeer] = [] # State parameters sent by the server self.parent_min_speed: Optional[int] = None @@ -104,7 +104,7 @@ def __init__(self, settings: Settings, event_bus: EventBus, network: Network): self.register_listeners() - self._potential_parent_tasks: List[asyncio.Task] = [] + self._potential_parent_tasks: list[asyncio.Task] = [] def register_listeners(self): self._event_bus.register( @@ -118,7 +118,7 @@ def register_listeners(self): self._event_bus.register( SessionDestroyedEvent, self._on_session_destroyed) - def _get_advertised_branch_values(self) -> Tuple[str, int]: + def _get_advertised_branch_values(self) -> tuple[str, int]: """Returns the advertised branch values. These values are to be sent to the children and the server to let them know where we are in the distributed tree. @@ -634,7 +634,7 @@ async def send_messages_to_children(self, *messages: Union[MessageDataclass, byt if child.connection: child.connection.queue_messages(*messages) - async def stop(self) -> List[asyncio.Task]: + async def stop(self) -> list[asyncio.Task]: """Cancels all pending tasks :return: a list of tasks that have been cancelled so that they can be @@ -642,7 +642,7 @@ async def stop(self) -> List[asyncio.Task]: """ return self._cancel_potential_parent_tasks() - def _cancel_potential_parent_tasks(self) -> List[asyncio.Task]: + def _cancel_potential_parent_tasks(self) -> list[asyncio.Task]: cancelled_tasks = [] for task in self._potential_parent_tasks: diff --git a/src/aioslsk/events.py b/src/aioslsk/events.py index 98de39be..60b4a6c2 100644 --- a/src/aioslsk/events.py +++ b/src/aioslsk/events.py @@ -1,21 +1,10 @@ from __future__ import annotations import asyncio +from collections.abc import Callable, Coroutine from dataclasses import dataclass import inspect import logging -from typing import ( - Any, - Callable, - Coroutine, - Dict, - List, - Optional, - Tuple, - Type, - TypeVar, - TYPE_CHECKING, - Union -) +from typing import Any, Optional, TypeVar, TYPE_CHECKING, Union from .room.model import Room, RoomMessage from .user.model import ChatMessage, User @@ -91,7 +80,7 @@ # Internal functions -def on_message(message_class: Type[MessageDataclass]): +def on_message(message_class: type[MessageDataclass]): """Decorator for methods listening to specific `MessageData` events""" def register(event_func): event_func._registered_message = message_class @@ -100,9 +89,9 @@ def register(event_func): return register -def build_message_map(obj: object) -> Dict[Type[MessageDataclass], Callable]: +def build_message_map(obj: object) -> dict[type[MessageDataclass], Callable]: methods = inspect.getmembers(obj, predicate=inspect.ismethod) - mapping: Dict[Type[MessageDataclass], Callable] = {} + mapping: dict[type[MessageDataclass], Callable] = {} for _, method in methods: registered_message = getattr(method, '_registered_message', None) if registered_message: @@ -115,9 +104,9 @@ def build_message_map(obj: object) -> Dict[Type[MessageDataclass], Callable]: class EventBus: def __init__(self): - self._events: Dict[Type[Event], List[Tuple[int, EventListener]]] = {} + self._events: dict[type[Event], list[tuple[int, EventListener]]] = {} - def register(self, event_class: Type[E], listener: EventListener, priority: int = 100): + def register(self, event_class: type[E], listener: EventListener, priority: int = 100): """Registers an event listener to listen on an event class. The order in which the listeners are called can be managed using the ``priority`` parameter @@ -238,7 +227,7 @@ class UserInfoUpdateEvent(Event): @dataclass(frozen=True) class RoomListEvent(Event): - rooms: List[Room] + rooms: list[Room] raw_message: RoomList.Response @@ -252,7 +241,7 @@ class RoomMessageEvent(Event): class RoomTickersEvent(Event): """Emitted when a list of tickers has been received for a room""" room: Room - tickers: Dict[str, str] + tickers: dict[str, str] raw_message: RoomTickers.Response @@ -348,7 +337,7 @@ class RoomOperatorRevokedEvent(Event): class RoomOperatorsEvent(Event): """Emitted when the server sends us a list of operators in a room""" room: Room - operators: List[User] + operators: list[User] raw_message: PrivateRoomOperators.Response @@ -358,7 +347,7 @@ class RoomMembersEvent(Event): of members always excludes the owner of the room """ room: Room - members: List[User] + members: list[User] raw_message: PrivateRoomMembers.Response @@ -398,50 +387,50 @@ class SearchRequestReceivedEvent(Event): @dataclass(frozen=True) class SimilarUsersEvent(Event): - users: List[Tuple[User, int]] + users: list[tuple[User, int]] raw_message: GetSimilarUsers.Response @dataclass(frozen=True) class ItemSimilarUsersEvent(Event): item: str - users: List[User] + users: list[User] raw_message: GetItemSimilarUsers.Response @dataclass(frozen=True) class RecommendationsEvent(Event): - recommendations: List[Recommendation] - unrecommendations: List[Recommendation] + recommendations: list[Recommendation] + unrecommendations: list[Recommendation] raw_message: GetRecommendations.Response @dataclass(frozen=True) class GlobalRecommendationsEvent(Event): - recommendations: List[Recommendation] - unrecommendations: List[Recommendation] + recommendations: list[Recommendation] + unrecommendations: list[Recommendation] raw_message: GetGlobalRecommendations.Response @dataclass(frozen=True) class ItemRecommendationsEvent(Event): item: str - recommendations: List[Recommendation] + recommendations: list[Recommendation] raw_message: GetItemRecommendations.Response @dataclass(frozen=True) class UserInterestsEvent(Event): user: User - interests: List[str] - hated_interests: List[str] + interests: list[str] + hated_interests: list[str] raw_message: GetUserInterests.Response @dataclass(frozen=True) class PrivilegedUsersEvent(Event): """Emitted when the list of privileged users has been received""" - users: List[User] + users: list[User] raw_message: PrivilegedUsers.Response @@ -466,8 +455,8 @@ class PrivilegesUpdateEvent(Event): @dataclass(frozen=True) class UserSharesReplyEvent(Event): user: User - directories: List[DirectoryData] - locked_directories: List[DirectoryData] + directories: list[DirectoryData] + locked_directories: list[DirectoryData] raw_message: PeerSharesReply.Request @@ -475,7 +464,7 @@ class UserSharesReplyEvent(Event): class UserDirectoryEvent(Event): user: User directory: str - directories: List[DirectoryData] + directories: list[DirectoryData] raw_message: PeerDirectoryContentsReply.Request @@ -502,7 +491,7 @@ class TransferProgressEvent(Event): since the previous event. If there are no updates the event will no be called """ - updates: List[Tuple[Transfer, TransferProgressSnapshot, TransferProgressSnapshot]] + updates: list[tuple[Transfer, TransferProgressSnapshot, TransferProgressSnapshot]] """List of progress updates: transfer instance, previous snapshot, current snapshot """ diff --git a/src/aioslsk/log_utils.py b/src/aioslsk/log_utils.py index 716a4f08..029a3717 100644 --- a/src/aioslsk/log_utils.py +++ b/src/aioslsk/log_utils.py @@ -1,11 +1,12 @@ +from collections.abc import Collection import logging -from typing import Collection, Type, Union +from typing import Union from .exceptions import AioSlskException from .protocol.primitives import MessageDataclass from .protocol import messages -def resolve(message_class: str) -> Type[MessageDataclass]: +def resolve(message_class: str) -> type[MessageDataclass]: message_root, req_resp = message_class.split('.') if root_message := getattr(messages, message_root, None): @@ -22,7 +23,7 @@ def resolve(message_class: str) -> Type[MessageDataclass]: class MessageFilter(logging.Filter): """Logging filter for protocol messages""" - def __init__(self, message_types: Collection[Union[Type[MessageDataclass], str]]): + def __init__(self, message_types: Collection[Union[type[MessageDataclass], str]]): super().__init__() converted = [] for message_type in message_types: @@ -32,7 +33,7 @@ def __init__(self, message_types: Collection[Union[Type[MessageDataclass], str]] mtype = message_type converted.append(mtype) - self.message_types: Collection[Type[MessageDataclass]] = converted + self.message_types: Collection[type[MessageDataclass]] = converted def filter(self, record: logging.LogRecord) -> bool: if message_type := getattr(record, 'message_type', None): diff --git a/src/aioslsk/naming.py b/src/aioslsk/naming.py index afbfec8e..9ee05a18 100644 --- a/src/aioslsk/naming.py +++ b/src/aioslsk/naming.py @@ -1,6 +1,5 @@ import os import re -from typing import List, Tuple from .utils import split_remote_path @@ -11,7 +10,7 @@ class NamingStrategy: def should_be_applied(self, local_dir: str, local_filename: str) -> bool: return True - def apply(self, remote_path: str, local_dir: str, local_filename: str) -> Tuple[str, str]: + def apply(self, remote_path: str, local_dir: str, local_filename: str) -> tuple[str, str]: """Apply the naming changes :param remote_path: the original remote path @@ -28,14 +27,14 @@ class DefaultNamingStrategy(NamingStrategy): `remote_path` parameter. The `local_filename` parameter is ignored """ - def apply(self, remote_path: str, local_dir: str, local_filename: str) -> Tuple[str, str]: + def apply(self, remote_path: str, local_dir: str, local_filename: str) -> tuple[str, str]: return local_dir, split_remote_path(remote_path)[-1] class KeepDirectoryStrategy(NamingStrategy): """Keeps the original directory the remote file was in""" - def apply(self, remote_path: str, local_dir: str, local_filename: str) -> Tuple[str, str]: + def apply(self, remote_path: str, local_dir: str, local_filename: str) -> tuple[str, str]: # -1 filename # -2 the containing directory remote_path_parts = split_remote_path(remote_path) @@ -67,7 +66,7 @@ class NumberDuplicateStrategy(DuplicateNamingStrategy): """ PATTERN = r' \((\d+)\)' - def apply(self, remote_path: str, local_dir: str, local_filename: str) -> Tuple[str, str]: + def apply(self, remote_path: str, local_dir: str, local_filename: str) -> tuple[str, str]: # Find all files which are already numbered filename, extension = os.path.splitext(local_filename) pattern = re.escape(filename) + self.PATTERN + re.escape(extension) @@ -88,7 +87,7 @@ def apply(self, remote_path: str, local_dir: str, local_filename: str) -> Tuple[ return local_dir, new_filename -def chain_strategies(strategies: List[NamingStrategy], remote_path: str, local_dir: str) -> Tuple[str, str]: +def chain_strategies(strategies: list[NamingStrategy], remote_path: str, local_dir: str) -> tuple[str, str]: """Chains strategies together to find the target location and filename to which the file should be written. diff --git a/src/aioslsk/network/connection.py b/src/aioslsk/network/connection.py index abad66ee..1b044b61 100644 --- a/src/aioslsk/network/connection.py +++ b/src/aioslsk/network/connection.py @@ -3,16 +3,9 @@ from aiofiles.threadpool.binary import AsyncBufferedIOBase, AsyncBufferedReader import asyncio from async_timeout import Timeout, timeout as atimeout +from collections.abc import Callable, Coroutine from enum import auto, Enum -from typing import ( - Any, - Callable, - Coroutine, - List, - Optional, - TYPE_CHECKING, - Union, -) +from typing import Any, Optional, TYPE_CHECKING, Union import logging import socket import struct @@ -206,7 +199,7 @@ def __init__( self._writer: Optional[asyncio.StreamWriter] = None self._reader_task: Optional[asyncio.Task] = None - self._queued_messages: List[asyncio.Task] = [] + self._queued_messages: list[asyncio.Task] = [] self._read_timeout_object: Optional[Timeout] = None self.read_timeout: float = read_timeout @@ -439,7 +432,7 @@ def queue_message(self, message: Union[bytes, MessageDataclass]) -> asyncio.Task task.add_done_callback(self._queued_messages.remove) return task - def queue_messages(self, *messages: Union[bytes, MessageDataclass]) -> List[asyncio.Task]: + def queue_messages(self, *messages: Union[bytes, MessageDataclass]) -> list[asyncio.Task]: return [ self.queue_message(message) for message in messages diff --git a/src/aioslsk/network/network.py b/src/aioslsk/network/network.py index ccc9e82c..98933ec3 100644 --- a/src/aioslsk/network/network.py +++ b/src/aioslsk/network/network.py @@ -9,13 +9,9 @@ import logging from typing import ( Any, - Dict, - List, Optional, Union, - Tuple, TypeVar, - Type, TYPE_CHECKING ) @@ -74,7 +70,7 @@ T = TypeVar('T', bound='MessageDataclass') -ListeningConnections = Tuple[Optional[ListeningConnection], Optional[ListeningConnection]] +ListeningConnections = tuple[Optional[ListeningConnection], Optional[ListeningConnection]] class ListeningConnectionErrorMode(enum.Enum): @@ -96,16 +92,16 @@ class ExpectedResponse(asyncio.Future): """Future for an expected response message""" def __init__( - self, connection_class: Type[Union[PeerConnection, ServerConnection]], - message_class: Type[MessageDataclass], - peer: Optional[str] = None, fields: Optional[Dict[str, Any]] = None, + self, connection_class: type[Union[PeerConnection, ServerConnection]], + message_class: type[MessageDataclass], + peer: Optional[str] = None, fields: Optional[dict[str, Any]] = None, loop: Optional[asyncio.AbstractEventLoop] = None): super().__init__(loop=loop) - self.connection_class: Type[Union[PeerConnection, ServerConnection]] = connection_class - self.message_class: Type[MessageDataclass] = message_class + self.connection_class: type[Union[PeerConnection, ServerConnection]] = connection_class + self.message_class: type[MessageDataclass] = message_class self.peer: Optional[str] = peer - self.fields: Dict[str, Any] = {} if fields is None else fields + self.fields: dict[str, Any] = {} if fields is None else fields def matches(self, connection: DataConnection, response: MessageDataclass) -> bool: if connection.__class__ != self.connection_class: @@ -145,13 +141,13 @@ def __init__(self, settings: Settings, event_bus: EventBus): self._event_bus: EventBus = event_bus self._upnp = upnp.UPNP() self._ticket_generator = ticket_generator() - self._expected_response_futures: List[ExpectedResponse] = [] - self._expected_connection_futures: Dict[int, asyncio.Future] = {} + self._expected_response_futures: list[ExpectedResponse] = [] + self._expected_connection_futures: dict[int, asyncio.Future] = {} # List of connections self.server_connection: ServerConnection = self.create_server_connection() self.listening_connections: ListeningConnections = self.create_listening_connections() - self.peer_connections: List[PeerConnection] = [] + self.peer_connections: list[PeerConnection] = [] self._MESSAGE_MAP = build_message_map(self) @@ -162,10 +158,10 @@ def __init__(self, settings: Settings, event_bus: EventBus): self._settings.network.limits.download_speed_kbps) # Debugging - self._ip_overrides: Dict[str, str] = self._settings.debug.ip_overrides + self._ip_overrides: dict[str, str] = self._settings.debug.ip_overrides # Operational - self._create_peer_connection_tasks: List[asyncio.Task] = [] + self._create_peer_connection_tasks: list[asyncio.Task] = [] self._log_connections_task: BackgroundTask = BackgroundTask( interval=10, task_coro=self._log_connections_job, @@ -280,7 +276,7 @@ async def disconnect(self): """ self._cancel_all_tasks() - connections: List[Connection] = [self.server_connection] + connections: list[Connection] = [self.server_connection] connections.extend(self.peer_connections) connections.extend(conn for conn in self.listening_connections if conn) @@ -291,7 +287,7 @@ async def disconnect(self): ) logger.info("network disconnected") - def get_listening_ports(self) -> Tuple[int, int]: + def get_listening_ports(self) -> tuple[int, int]: """Gets the currently connected listening ports :return: ``tuple`` with 2 elements: the non-obfuscated- and obfuscated @@ -398,7 +394,7 @@ async def _upnp_job(self): interval, the check interval could be shorter if the job detects that the lease duration of a port is about to expire """ - def filter_mapped_port(mapped_ports: List[PortMappingEntry], internal_ip: str, port: int) -> PortMappingEntry: + def filter_mapped_port(mapped_ports: list[PortMappingEntry], internal_ip: str, port: int) -> PortMappingEntry: expected_values = (True, 'TCP', IPv4Address(internal_ip), port) for mapped_port in mapped_ports: values = ( @@ -480,7 +476,7 @@ async def start_upnp_job(self): def stop_upnp_job(self): self._upnp_task.cancel() - def select_port(self, port, obfuscated_port) -> Tuple[int, bool]: + def select_port(self, port, obfuscated_port) -> tuple[int, bool]: """Selects the port used for making a connection. This attempts to take into account the ``network.peer.obfuscate`` parameter however falls back on using whatever port is available if necessary @@ -564,7 +560,7 @@ async def create_peer_connection( PeerInitializedEvent(connection, requested=True)) return connection - async def _get_peer_address(self, username: str) -> Tuple[str, int, int]: + async def _get_peer_address(self, username: str) -> tuple[str, int, int]: """Requests the peer address for the given ``username`` from the server. :raise PeerConnectionError: if no IP address or no valid ports were @@ -612,7 +608,7 @@ def _remove_connection_future(self, ticket: int, connection_future: asyncio.Futu self._expected_connection_futures.pop(ticket) def create_server_response_future( - self, message_class: Type[T], fields: Optional[Dict[str, Any]] = None) -> ExpectedResponse: + self, message_class: type[T], fields: Optional[dict[str, Any]] = None) -> ExpectedResponse: """Creates a future for a server message to arrive, the message must match the ``message_class`` and fields defined in the keyword arguments. @@ -638,7 +634,7 @@ def register_response_future(self, expected_response: ExpectedResponse): expected_response.add_done_callback(self._remove_response_future) async def wait_for_server_message( - self, message_class: Type[T], fields: Optional[Dict[str, Any]] = None, timeout: float = 10) -> T: + self, message_class: type[T], fields: Optional[dict[str, Any]] = None, timeout: float = 10) -> T: """Waits for a message from the server :param message_class: Class of the expected server message @@ -659,7 +655,7 @@ async def wait_for_server_message( return response def create_peer_response_future( - self, peer: str, message_class: Type[T], fields: Optional[Dict[str, Any]] = None) -> ExpectedResponse: + self, peer: str, message_class: type[T], fields: Optional[dict[str, Any]] = None) -> ExpectedResponse: """Creates a future for a peer message to arrive, the message must match the ``message_class`` and fields defined in the keyword arguments and must be coming from a connection by ``peer``. @@ -682,7 +678,7 @@ def create_peer_response_future( return future async def wait_for_peer_message( - self, peer: str, message_class: Type[T], fields: Optional[Dict[str, Any]] = None, timeout: float = 60) -> T: + self, peer: str, message_class: type[T], fields: Optional[dict[str, Any]] = None, timeout: float = 60) -> T: future = self.create_peer_response_future( peer=peer, @@ -698,7 +694,7 @@ async def wait_for_peer_message( return response - def get_peer_connections(self, username: str, typ: str) -> List[PeerConnection]: + def get_peer_connections(self, username: str, typ: str) -> list[PeerConnection]: """Returns all connections for peer with given username and peer connection types. @@ -712,7 +708,7 @@ def get_peer_connections(self, username: str, typ: str) -> List[PeerConnection]: ] def get_active_peer_connections( - self, username: str, typ: str) -> List[PeerConnection]: + self, username: str, typ: str) -> list[PeerConnection]: """Return a list of currently active messaging connections for given peer connection type. @@ -901,7 +897,7 @@ async def send_peer_messages( if not raise_on_error: return list(zip(messages, results)) - def queue_server_messages(self, *messages: Union[bytes, MessageDataclass]) -> List[asyncio.Task]: + def queue_server_messages(self, *messages: Union[bytes, MessageDataclass]) -> list[asyncio.Task]: """Queues server messages :param messages: list of messages to queue diff --git a/src/aioslsk/network/upnp.py b/src/aioslsk/network/upnp.py index be6621b9..2e163aea 100644 --- a/src/aioslsk/network/upnp.py +++ b/src/aioslsk/network/upnp.py @@ -1,7 +1,6 @@ from __future__ import annotations from functools import partial from ipaddress import IPv4Address -from typing import List import logging from async_upnp_client.aiohttp import AiohttpRequester from async_upnp_client.client_factory import UpnpFactory @@ -24,8 +23,8 @@ class UPNP: def __init__(self): self._factory: UpnpFactory = UpnpFactory(AiohttpRequester()) - async def search_igd_devices(self, source_ip: str, timeout: int = UPNP_DEFAULT_SEARCH_TIMEOUT) -> List[IgdDevice]: - devices: List[IgdDevice] = [] + async def search_igd_devices(self, source_ip: str, timeout: int = UPNP_DEFAULT_SEARCH_TIMEOUT) -> list[IgdDevice]: + devices: list[IgdDevice] = [] logger.info("starting search for IGD devices") await async_search( partial(self._search_callback, devices), @@ -36,7 +35,7 @@ async def search_igd_devices(self, source_ip: str, timeout: int = UPNP_DEFAULT_S logger.info("found %d IGD devices", len(devices)) return devices - async def _search_callback(self, devices: List[IgdDevice], headers): + async def _search_callback(self, devices: list[IgdDevice], headers): if headers['ST'] not in IgdDevice.DEVICE_TYPES: return @@ -45,7 +44,7 @@ async def _search_callback(self, devices: List[IgdDevice], headers): devices.append(IgdDevice(device, None)) - async def get_mapped_ports(self, device: IgdDevice) -> List[PortMappingEntry]: + async def get_mapped_ports(self, device: IgdDevice) -> list[PortMappingEntry]: entry_count = await device.async_get_port_mapping_number_of_entries() if entry_count: @@ -55,7 +54,7 @@ async def get_mapped_ports(self, device: IgdDevice) -> List[PortMappingEntry]: else: return await self._get_mapped_ports_unknown(device) - async def _get_mapped_ports_known(self, device: IgdDevice, count: int) -> List[PortMappingEntry]: + async def _get_mapped_ports_known(self, device: IgdDevice, count: int) -> list[PortMappingEntry]: entries = [] for idx in range(count): logger.debug("getting port map with index %d on device %r", idx, device.name) @@ -72,7 +71,7 @@ async def _get_mapped_ports_known(self, device: IgdDevice, count: int) -> List[P return entries - async def _get_mapped_ports_unknown(self, device: IgdDevice) -> List[PortMappingEntry]: + async def _get_mapped_ports_unknown(self, device: IgdDevice) -> list[PortMappingEntry]: """Gets all mapped port entries for the given device when the length of the total amount of ports is not known. """ diff --git a/src/aioslsk/protocol/messages.py b/src/aioslsk/protocol/messages.py index 07377e10..59d6853c 100644 --- a/src/aioslsk/protocol/messages.py +++ b/src/aioslsk/protocol/messages.py @@ -18,7 +18,7 @@ """ from dataclasses import dataclass, field import logging -from typing import ClassVar, List, Optional +from typing import ClassVar, Optional from ..exceptions import UnknownMessageError from .primitives import ( @@ -265,13 +265,13 @@ class Request(MessageDataclass): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x0E) room: str = field(metadata={'type': string}) - users: List[str] = field(metadata={'type': array, 'subtype': string}) - users_status: List[int] = field(metadata={'type': array, 'subtype': uint32}) - users_stats: List[UserStats] = field(metadata={'type': array, 'subtype': UserStats}) - users_slots_free: List[int] = field(metadata={'type': array, 'subtype': uint32}) - users_countries: List[str] = field(metadata={'type': array, 'subtype': string}) + users: list[str] = field(metadata={'type': array, 'subtype': string}) + users_status: list[int] = field(metadata={'type': array, 'subtype': uint32}) + users_stats: list[UserStats] = field(metadata={'type': array, 'subtype': UserStats}) + users_slots_free: list[int] = field(metadata={'type': array, 'subtype': uint32}) + users_countries: list[str] = field(metadata={'type': array, 'subtype': string}) owner: Optional[str] = field(default=None, metadata={'type': string, 'optional': True}) - operators: Optional[List[str]] = field( + operators: Optional[list[str]] = field( default=None, metadata={ 'type': array, @@ -498,7 +498,7 @@ class Request(MessageDataclass): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x32) item: str = field(metadata={'type': string}) - recommendations: List[str] = field(metadata={'type': array, 'subtype': string}) + recommendations: list[str] = field(metadata={'type': array, 'subtype': string}) class AddInterest(ServerMessage): @@ -526,10 +526,10 @@ class Request(MessageDataclass): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x36) - recommendations: List[Recommendation] = field( + recommendations: list[Recommendation] = field( metadata={'type': array, 'subtype': Recommendation} ) - unrecommendations: List[Recommendation] = field( + unrecommendations: list[Recommendation] = field( metadata={'type': array, 'subtype': Recommendation} ) @@ -543,7 +543,7 @@ class Request(MessageDataclass): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x37) - interests: List[str] = field(metadata={'type': array, 'subtype': string}) + interests: list[str] = field(metadata={'type': array, 'subtype': string}) class GetGlobalRecommendations(ServerMessage): @@ -555,10 +555,10 @@ class Request(MessageDataclass): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x38) - recommendations: List[Recommendation] = field( + recommendations: list[Recommendation] = field( metadata={'type': array, 'subtype': Recommendation} ) - unrecommendations: List[Recommendation] = field( + unrecommendations: list[Recommendation] = field( metadata={'type': array, 'subtype': Recommendation} ) @@ -574,8 +574,8 @@ class Request(MessageDataclass): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x39) username: str = field(metadata={'type': string}) - interests: List[str] = field(metadata={'type': array, 'subtype': string}) - hated_interests: List[str] = field(metadata={'type': array, 'subtype': string}) + interests: list[str] = field(metadata={'type': array, 'subtype': string}) + hated_interests: list[str] = field(metadata={'type': array, 'subtype': string}) class ExecuteCommand(ServerMessage): @@ -584,7 +584,7 @@ class ExecuteCommand(ServerMessage): class Request(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x3A) command_type: str = field(metadata={'type': string}) - arguments: List[str] = field(metadata={'type': array, 'subtype': string}) + arguments: list[str] = field(metadata={'type': array, 'subtype': string}) class RoomList(ServerMessage): @@ -596,13 +596,13 @@ class Request(MessageDataclass): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x40) - rooms: List[str] = field(metadata={'type': array, 'subtype': string}) - rooms_user_count: List[int] = field(metadata={'type': array, 'subtype': uint32}) - rooms_private_owned: List[str] = field(metadata={'type': array, 'subtype': string}) - rooms_private_owned_user_count: List[int] = field(metadata={'type': array, 'subtype': uint32}) - rooms_private: List[str] = field(metadata={'type': array, 'subtype': string}) - rooms_private_user_count: List[int] = field(metadata={'type': array, 'subtype': uint32}) - rooms_private_operated: List[str] = field(metadata={'type': array, 'subtype': string}) + rooms: list[str] = field(metadata={'type': array, 'subtype': string}) + rooms_user_count: list[int] = field(metadata={'type': array, 'subtype': uint32}) + rooms_private_owned: list[str] = field(metadata={'type': array, 'subtype': string}) + rooms_private_owned_user_count: list[int] = field(metadata={'type': array, 'subtype': uint32}) + rooms_private: list[str] = field(metadata={'type': array, 'subtype': string}) + rooms_private_user_count: list[int] = field(metadata={'type': array, 'subtype': uint32}) + rooms_private_operated: list[str] = field(metadata={'type': array, 'subtype': string}) class ExactFileSearch(ServerMessage): @@ -645,11 +645,11 @@ class Request(MessageDataclass): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x43) - users: List[str] = field(metadata={'type': array, 'subtype': string}) - users_status: List[int] = field(metadata={'type': array, 'subtype': uint32}) - users_stats: List[UserStats] = field(metadata={'type': array, 'subtype': UserStats}) - users_slots_free: List[int] = field(metadata={'type': array, 'subtype': uint32}) - users_countries: List[str] = field(metadata={'type': array, 'subtype': string}) + users: list[str] = field(metadata={'type': array, 'subtype': string}) + users_status: list[int] = field(metadata={'type': array, 'subtype': uint32}) + users_stats: list[UserStats] = field(metadata={'type': array, 'subtype': UserStats}) + users_slots_free: list[int] = field(metadata={'type': array, 'subtype': uint32}) + users_countries: list[str] = field(metadata={'type': array, 'subtype': string}) class TunneledMessage(ServerMessage): @@ -678,7 +678,7 @@ class PrivilegedUsers(ServerMessage): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x45) - users: List[str] = field(metadata={'type': array, 'subtype': string}) + users: list[str] = field(metadata={'type': array, 'subtype': string}) class ToggleParentSearch(ServerMessage): @@ -805,7 +805,7 @@ class PotentialParents(ServerMessage): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x66) - entries: List[PotentialParent] = field(metadata={'type': array, 'subtype': PotentialParent}) + entries: list[PotentialParent] = field(metadata={'type': array, 'subtype': PotentialParent}) class WishlistSearch(ServerMessage): @@ -834,7 +834,7 @@ class Request(MessageDataclass): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x6E) - users: List[SimilarUser] = field(metadata={'type': array, 'subtype': SimilarUser}) + users: list[SimilarUser] = field(metadata={'type': array, 'subtype': SimilarUser}) class GetItemRecommendations(ServerMessage): @@ -848,7 +848,7 @@ class Request(MessageDataclass): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x6F) item: str = field(metadata={'type': string}) - recommendations: List[Recommendation] = field(metadata={'type': array, 'subtype': Recommendation}) + recommendations: list[Recommendation] = field(metadata={'type': array, 'subtype': Recommendation}) class GetItemSimilarUsers(ServerMessage): @@ -862,7 +862,7 @@ class Request(MessageDataclass): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x70) item: str = field(metadata={'type': string}) - usernames: List[str] = field(metadata={'type': array, 'subtype': string}) + usernames: list[str] = field(metadata={'type': array, 'subtype': string}) class RoomTickers(ServerMessage): @@ -871,7 +871,7 @@ class RoomTickers(ServerMessage): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x71) room: str = field(metadata={'type': string}) - tickers: List[RoomTicker] = field(metadata={'type': array, 'subtype': RoomTicker}) + tickers: list[RoomTicker] = field(metadata={'type': array, 'subtype': RoomTicker}) class RoomTickerAdded(ServerMessage): @@ -1013,7 +1013,7 @@ class PrivateRoomMembers(ServerMessage): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x85) room: str = field(metadata={'type': string}) - usernames: List[str] = field(metadata={'type': array, 'subtype': string}) + usernames: list[str] = field(metadata={'type': array, 'subtype': string}) class PrivateRoomGrantMembership(ServerMessage): @@ -1151,7 +1151,7 @@ class PrivateRoomOperators(ServerMessage): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x94) room: str = field(metadata={'type': string}) - usernames: List[str] = field(metadata={'type': array, 'subtype': string}) + usernames: list[str] = field(metadata={'type': array, 'subtype': string}) class PrivateChatMessageUsers(ServerMessage): @@ -1159,7 +1159,7 @@ class PrivateChatMessageUsers(ServerMessage): @dataclass(order=True) class Request(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x95) - usernames: List[str] = field(metadata={'type': array, 'subtype': string}) + usernames: list[str] = field(metadata={'type': array, 'subtype': string}) message: str = field(metadata={'type': string}) @@ -1198,7 +1198,7 @@ class Request(MessageDataclass): class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x99) query: str = field(metadata={'type': string}) - related_searches: List[str] = field(metadata={'type': array, 'subtype': string}) + related_searches: list[str] = field(metadata={'type': array, 'subtype': string}) class ExcludedSearchPhrases(ServerMessage): @@ -1206,7 +1206,7 @@ class ExcludedSearchPhrases(ServerMessage): @dataclass(order=True) class Response(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0xA0) - phrases: List[str] = field(metadata={'type': array, 'subtype': string}) + phrases: list[str] = field(metadata={'type': array, 'subtype': string}) class CannotConnect(ServerMessage): @@ -1286,14 +1286,14 @@ class PeerSharesReply(PeerMessage): @dataclass(order=True) class Request(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x05) - directories: List[DirectoryData] = field( + directories: list[DirectoryData] = field( metadata={'type': array, 'subtype': DirectoryData} ) # Unknown field that always seems to be 0, possibilities: # * This was another list, but it always empty (not tested) # * This is a ticket: See explanation of ticket in PeerSharesRequest unknown: int = field(default=0, metadata={'type': uint32}) - locked_directories: Optional[List[DirectoryData]] = field( + locked_directories: Optional[list[DirectoryData]] = field( default=None, metadata={'type': array, 'subtype': DirectoryData, 'optional': True} ) @@ -1313,7 +1313,7 @@ class Request(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x09) username: str = field(metadata={'type': string}) ticket: int = field(metadata={'type': uint32}) - results: List[FileData] = field(metadata={'type': array, 'subtype': FileData}) + results: list[FileData] = field(metadata={'type': array, 'subtype': FileData}) has_slots_free: bool = field(metadata={'type': boolean}) avg_speed: int = field(metadata={'type': uint32}) # Note: queue_size and unknown. queue_size is described as uint64 in the @@ -1322,7 +1322,7 @@ class Request(MessageDataclass): # locked results: the same can be seen in PeerSharesReply queue_size: int = field(metadata={'type': uint32}) unknown: Optional[int] = field(default=0, metadata={'type': uint32}) - locked_results: Optional[List[FileData]] = field( + locked_results: Optional[list[FileData]] = field( default=None, metadata={'type': array, 'subtype': FileData, 'optional': True} ) @@ -1373,7 +1373,7 @@ class Request(MessageDataclass): MESSAGE_ID: ClassVar[uint32] = uint32(0x25) ticket: int = field(metadata={'type': uint32}) directory: str = field(metadata={'type': string}) - directories: List[DirectoryData] = field(metadata={'type': array, 'subtype': DirectoryData}) + directories: list[DirectoryData] = field(metadata={'type': array, 'subtype': DirectoryData}) def serialize(self, compress: bool = True) -> bytes: return super().serialize(compress) diff --git a/src/aioslsk/protocol/primitives.py b/src/aioslsk/protocol/primitives.py index 3a4205b6..c32aa8ff 100644 --- a/src/aioslsk/protocol/primitives.py +++ b/src/aioslsk/protocol/primitives.py @@ -39,12 +39,8 @@ from typing import ( Any, ClassVar, - Dict, - List, Optional, Protocol, - Tuple, - Type, TypeVar, Union ) @@ -61,7 +57,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: ... @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, Self]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, Self]: ... def serialize(self, *args, **kwargs) -> bytes: @@ -82,7 +78,7 @@ def serialize(self) -> bytes: return self.STRUCT.pack(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, int]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, int]: return pos + cls.STRUCT.size, cls.STRUCT.unpack_from(data, offset=pos)[0] @@ -93,7 +89,7 @@ def serialize(self) -> bytes: return self.STRUCT.pack(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, int]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, int]: return pos + cls.STRUCT.size, cls.STRUCT.unpack_from(data, offset=pos)[0] @@ -104,7 +100,7 @@ def serialize(self) -> bytes: return self.STRUCT.pack(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, int]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, int]: return pos + cls.STRUCT.size, cls.STRUCT.unpack_from(data, offset=pos)[0] @@ -116,7 +112,7 @@ def serialize(self) -> bytes: return self.STRUCT.pack(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, int]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, int]: return pos + cls.STRUCT_SIZE, cls.STRUCT.unpack_from(data, offset=pos)[0] @@ -127,7 +123,7 @@ def serialize(self) -> bytes: return self.STRUCT.pack(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, int]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, int]: return pos + cls.STRUCT.size, cls.STRUCT.unpack_from(data, offset=pos)[0] @@ -139,7 +135,7 @@ def serialize(self, encoding: str = 'utf-8') -> bytes: return uint32(len(byte_string)).serialize() + byte_string @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, str]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, str]: pos_after_len, length = uint32.deserialize(pos, data) end_pos = pos_after_len + length value = data[pos_after_len:end_pos] @@ -156,7 +152,7 @@ def serialize(self) -> bytes: return uint32(length).serialize() + bytes(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, bytes]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, bytes]: pos_after_len, length = uint32.deserialize(pos, data) value = data[pos_after_len:pos_after_len + length] return pos_after_len + length, value @@ -170,7 +166,7 @@ def serialize(self) -> bytes: return self.STRUCT.pack(bytes(reversed(ip_b))) @classmethod - def deserialize(cls, pos: int, data) -> Tuple[int, str]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, str]: value = cls.STRUCT.unpack(data[pos:pos + 4])[0] ip_addr = socket.inet_ntoa(bytes(reversed(value))) return pos + 4, ip_addr @@ -183,13 +179,13 @@ def serialize(self) -> bytes: return self.STRUCT.pack(self) @classmethod - def deserialize(cls, pos: int, data: bytes) -> Tuple[int, bool]: + def deserialize(cls, pos: int, data: bytes) -> tuple[int, bool]: return pos + cls.STRUCT.size, cls.STRUCT.unpack_from(data, offset=pos)[0] class array(list): - def serialize(self, element_type: Type[Serializable]) -> bytes: + def serialize(self, element_type: type[Serializable]) -> bytes: body = uint32(len(self)).serialize() is_protocoldc = is_dataclass(element_type) for value in self: @@ -200,7 +196,7 @@ def serialize(self, element_type: Type[Serializable]) -> bytes: return body @classmethod - def deserialize(cls, pos: int, data: bytes, element_type: Type[T]) -> Tuple[int, List[T]]: + def deserialize(cls, pos: int, data: bytes, element_type: type[T]) -> tuple[int, list[T]]: items = [] pos, array_len = uint32.deserialize(pos, data) for _ in range(array_len): @@ -225,7 +221,7 @@ class CustomDataclass(ProtocolDataclass): password: str = field(metadata={'type': string}) has_privileges: bool = field(metadata={'type': boolean}) """ - _CACHED_FIELDS: Optional[Tuple[Field, ...]] = None + _CACHED_FIELDS: Optional[tuple[Field, ...]] = None """Cache version of the ``dataclasses.fields`` return for the current class """ @@ -245,7 +241,7 @@ def serialize(self) -> bytes: # Serialize try: - proto_type: Type[Serializable] = obj_field.metadata['type'] + proto_type: type[Serializable] = obj_field.metadata['type'] except KeyError: raise Exception(f"no 'type' for field {obj_field.name!r} defined") @@ -275,7 +271,7 @@ def deserialize(cls, pos: int, message: bytes): # cause too many issues cls._CACHED_FIELDS = fields(cls) # type: ignore[arg-type] - field_map: Dict[str, Any] = {} + field_map: dict[str, Any] = {} for obj_field in cls._CACHED_FIELDS: if not cls._field_needs_deserialization(obj_field, field_map, pos, message): continue @@ -298,7 +294,7 @@ def deserialize(cls, pos: int, message: bytes): return pos, cls(**field_map) @classmethod - def _field_needs_deserialization(cls, field: Field, field_map: Dict[str, Any], pos: int, message: bytes): + def _field_needs_deserialization(cls, field: Field, field_map: dict[str, Any], pos: int, message: bytes): # For if_true and if_false we need to return only if the condition is # is false as we still want to check the 'optional' field if 'if_true' in field.metadata: @@ -451,7 +447,7 @@ class FileData(ProtocolDataclass): filename: str = field(metadata={'type': string}) filesize: int = field(metadata={'type': uint64}) extension: str = field(metadata={'type': string}) - attributes: List[Attribute] = field(metadata={'type': array, 'subtype': Attribute}) + attributes: list[Attribute] = field(metadata={'type': array, 'subtype': Attribute}) @classmethod def deserialize(cls, pos: int, message: bytes): @@ -481,7 +477,7 @@ def serialize(self) -> bytes: @dataclass(frozen=True, order=True) class DirectoryData(ProtocolDataclass): name: str = field(metadata={'type': string}) - files: List[FileData] = field(metadata={'type': array, 'subtype': FileData}) + files: list[FileData] = field(metadata={'type': array, 'subtype': FileData}) @classmethod def deserialize(cls, pos: int, message: bytes): diff --git a/src/aioslsk/room/manager.py b/src/aioslsk/room/manager.py index b997d7de..12602e7d 100644 --- a/src/aioslsk/room/manager.py +++ b/src/aioslsk/room/manager.py @@ -1,7 +1,6 @@ from collections import OrderedDict import logging import time -from typing import Dict, List from ..base_manager import BaseManager from ..events import ( @@ -71,33 +70,33 @@ def __init__( self._user_manager: UserManager = user_manager self._network: Network = network - self._rooms: Dict[str, Room] = dict() + self._rooms: dict[str, Room] = dict() self._MESSAGE_MAP = build_message_map(self) self.register_listeners() @property - def rooms(self) -> Dict[str, Room]: + def rooms(self) -> dict[str, Room]: return self._rooms - def get_joined_rooms(self) -> List[Room]: + def get_joined_rooms(self) -> list[Room]: return [room for room in self._rooms.values() if room.joined] - def get_private_rooms(self) -> List[Room]: + def get_private_rooms(self) -> list[Room]: return [room for room in self._rooms.values() if room.private] - def get_public_rooms(self) -> List[Room]: + def get_public_rooms(self) -> list[Room]: return [room for room in self._rooms.values() if not room.private] - def get_owned_rooms(self) -> List[Room]: + def get_owned_rooms(self) -> list[Room]: me = self._user_manager.get_self() return [ room for room in self._rooms.values() if room.owner == me.name ] - def get_operated_rooms(self) -> List[Room]: + def get_operated_rooms(self) -> list[Room]: me = self._user_manager.get_self() return [ room for room in self._rooms.values() diff --git a/src/aioslsk/room/model.py b/src/aioslsk/room/model.py index 492cbde8..d2dd9fce 100644 --- a/src/aioslsk/room/model.py +++ b/src/aioslsk/room/model.py @@ -1,6 +1,6 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Dict, List, Optional, Set +from typing import Optional from ..user.model import User @@ -8,19 +8,19 @@ class Room: name: str private: bool = False - users: List[User] = field(default_factory=list) + users: list[User] = field(default_factory=list) """Current list of joined users""" joined: bool = False user_count: int = 0 - tickers: Dict[str, str] = field(default_factory=dict) + tickers: dict[str, str] = field(default_factory=dict) """Room tickers (room wall)""" # Only for private rooms - members: Set[str] = field(default_factory=set) + members: set[str] = field(default_factory=set) """For private rooms, names of members of the room (excludes owner)""" owner: Optional[str] = None """For private rooms, name of the room owner""" - operators: Set[str] = field(default_factory=set) + operators: set[str] = field(default_factory=set) """For private rooms, names of operators""" def add_user(self, user: User): diff --git a/src/aioslsk/search/manager.py b/src/aioslsk/search/manager.py index 1e24b6ac..ac4ae15f 100644 --- a/src/aioslsk/search/manager.py +++ b/src/aioslsk/search/manager.py @@ -2,7 +2,7 @@ from collections import deque from functools import partial import logging -from typing import Deque, Dict, List, Optional, Union +from typing import Optional, Union from ..base_manager import BaseManager from ..events import ( @@ -67,20 +67,20 @@ def __init__( self._ticket_generator = ticket_generator() self._session: Optional[Session] = None - self.received_searches: Deque[ReceivedSearch] = deque( + self.received_searches: deque[ReceivedSearch] = deque( list(), maxlen=self._settings.searches.receive.store_amount) - self.requests: Dict[int, SearchRequest] = {} + self.requests: dict[int, SearchRequest] = {} # Server variables self.search_inactivity_timeout: Optional[int] = None self.wishlist_interval: Optional[int] = None - self.excluded_search_phrases: List[str] = [] + self.excluded_search_phrases: list[str] = [] self.register_listeners() self._MESSAGE_MAP = build_message_map(self) - self._search_reply_tasks: List[asyncio.Task] = [] + self._search_reply_tasks: list[asyncio.Task] = [] self._wishlist_task: BackgroundTask = BackgroundTask( interval=600, task_coro=self._wishlist_job, @@ -381,7 +381,7 @@ async def _on_session_initialized(self, event: SessionInitializedEvent): async def _on_session_destroyed(self, event: SessionDestroyedEvent): self._session = None - async def stop(self) -> List[asyncio.Task]: + async def stop(self) -> list[asyncio.Task]: """Cancels all pending tasks :return: a list of tasks that have been cancelled so that they can be diff --git a/src/aioslsk/search/model.py b/src/aioslsk/search/model.py index bdc277d8..1cb5285d 100644 --- a/src/aioslsk/search/model.py +++ b/src/aioslsk/search/model.py @@ -1,9 +1,10 @@ +from collections.abc import Callable, Generator from dataclasses import dataclass, field import datetime from enum import auto, Enum import logging import re -from typing import Callable, Generator, List, Optional, Set +from typing import Optional from ..protocol.primitives import FileData from ..shares.utils import create_term_pattern @@ -32,9 +33,9 @@ class ReceivedSearch: @dataclass class SearchQuery: query: str - include_terms: Set[str] = field(default_factory=set) - exclude_terms: Set[str] = field(default_factory=set) - wildcard_terms: Set[str] = field(default_factory=set) + include_terms: set[str] = field(default_factory=set) + exclude_terms: set[str] = field(default_factory=set) + wildcard_terms: set[str] = field(default_factory=set) @classmethod def parse(cls, query: str) -> 'SearchQuery': @@ -88,8 +89,8 @@ class SearchResult: avg_speed: int = 0 queue_size: int = 0 - shared_items: List[FileData] = field(default_factory=list) - locked_results: List[FileData] = field(default_factory=list) + shared_items: list[FileData] = field(default_factory=list) + locked_results: list[FileData] = field(default_factory=list) @dataclass @@ -101,5 +102,5 @@ class SearchRequest: room: Optional[str] = None username: Optional[str] = None - results: List[SearchResult] = field(default_factory=list) + results: list[SearchResult] = field(default_factory=list) started: datetime.datetime = field(default_factory=datetime.datetime.now) diff --git a/src/aioslsk/settings.py b/src/aioslsk/settings.py index cf9ec180..0ea8d958 100644 --- a/src/aioslsk/settings.py +++ b/src/aioslsk/settings.py @@ -1,5 +1,5 @@ import os -from typing import Dict, List, Optional, Set +from typing import Optional from pydantic import BaseModel, Field, model_validator from pydantic_settings import BaseSettings @@ -16,7 +16,7 @@ class SharedDirectorySettingEntry(BaseModel, validate_assignment=True): path: str share_mode: DirectoryShareMode = DirectoryShareMode.EVERYONE - users: List[str] = Field(default_factory=list) + users: list[str] = Field(default_factory=list) class WishlistSettingEntry(BaseModel, validate_assignment=True): @@ -92,11 +92,11 @@ class SearchReceiveSettings(BaseModel, validate_assignment=True): class SearchSettings(BaseModel, validate_assignment=True): send: SearchSendSettings = Field(default_factory=SearchSendSettings) receive: SearchReceiveSettings = Field(default_factory=SearchReceiveSettings) - wishlist: List[WishlistSettingEntry] = Field(default_factory=list) + wishlist: list[WishlistSettingEntry] = Field(default_factory=list) @model_validator(mode='before') @classmethod - def handle_max_result_alias(cls, values: Dict): + def handle_max_result_alias(cls, values: dict): if 'max_results' in values: value = values.pop('max_results') if 'receive' not in values: @@ -127,28 +127,28 @@ class TransfersSettings(BaseModel, validate_assignment=True): class SharesSettings(BaseModel, validate_assignment=True): scan_on_start: bool = True download: str = os.getcwd() - directories: List[SharedDirectorySettingEntry] = Field(default_factory=list) + directories: list[SharedDirectorySettingEntry] = Field(default_factory=list) class RoomsSettings(BaseModel, validate_assignment=True): auto_join: bool = True private_room_invites: bool = True - favorites: Set[str] = Field(default_factory=set) + favorites: set[str] = Field(default_factory=set) class UsersSettings(BaseModel, validate_assignment=True): - friends: Set[str] = Field(default_factory=set) - blocked: Set[str] = Field(default_factory=set) + friends: set[str] = Field(default_factory=set) + blocked: set[str] = Field(default_factory=set) class InterestsSettings(BaseModel, validate_assignment=True): - liked: Set[str] = Field(default_factory=set) - hated: Set[str] = Field(default_factory=set) + liked: set[str] = Field(default_factory=set) + hated: set[str] = Field(default_factory=set) class DebugSettings(BaseModel, validate_assignment=True): search_for_parent: bool = True - ip_overrides: Dict[str, str] = Field(default_factory=dict) + ip_overrides: dict[str, str] = Field(default_factory=dict) log_connection_count: bool = False diff --git a/src/aioslsk/shares/cache.py b/src/aioslsk/shares/cache.py index b10a96e0..b7b5a1b9 100644 --- a/src/aioslsk/shares/cache.py +++ b/src/aioslsk/shares/cache.py @@ -1,25 +1,25 @@ import os import shelve -from typing import List, Protocol +from typing import Protocol from .model import SharedDirectory class SharesCache(Protocol): """Abstract base class for storing shares""" - def read(self) -> List[SharedDirectory]: + def read(self) -> list[SharedDirectory]: ... - def write(self, shared_directories: List[SharedDirectory]): + def write(self, shared_directories: list[SharedDirectory]): ... class SharesNullCache: - def read(self) -> List[SharedDirectory]: # pragma: no cover + def read(self) -> list[SharedDirectory]: # pragma: no cover return [] - def write(self, shared_directories: List[SharedDirectory]): # pragma: no cover + def write(self, shared_directories: list[SharedDirectory]): # pragma: no cover pass @@ -34,9 +34,9 @@ def __init__(self, data_directory: str, filename: str = DEFAULT_FILENAME): def _get_index_path(self) -> str: return os.path.join(self.data_directory, self.filename) - def read(self) -> List[SharedDirectory]: + def read(self) -> list[SharedDirectory]: with shelve.open(self._get_index_path(), 'c') as db: - directories: List[SharedDirectory] = db.get('index', list()) + directories: list[SharedDirectory] = db.get('index', list()) for directory in directories: new_items = set() for item in directory.items: @@ -45,6 +45,6 @@ def read(self) -> List[SharedDirectory]: directory.items = new_items return directories - def write(self, shared_directories: List[SharedDirectory]): + def write(self, shared_directories: list[SharedDirectory]): with shelve.open(self._get_index_path(), 'c') as db: db['index'] = shared_directories diff --git a/src/aioslsk/shares/manager.py b/src/aioslsk/shares/manager.py index e496940c..e3c5df4d 100644 --- a/src/aioslsk/shares/manager.py +++ b/src/aioslsk/shares/manager.py @@ -1,5 +1,6 @@ from aiofiles import os as asyncos import asyncio +from collections.abc import Callable from concurrent.futures import Executor from functools import partial import logging @@ -9,15 +10,7 @@ import re import sys import time -from typing import ( - Callable, - Dict, - List, - Optional, - Set, - Tuple, - Union, -) +from typing import Optional, Union import uuid from weakref import WeakSet @@ -50,7 +43,7 @@ logger = logging.getLogger(__name__) -ItemAttributes = List[Tuple[int, int]] +ItemAttributes = list[tuple[int, int]] ExecutorFactory = Callable[[], Executor] _COMPRESSED_FORMATS = [ @@ -69,7 +62,7 @@ def scan_directory( shared_directory: SharedDirectory, - children: Optional[List[SharedDirectory]] = None) -> Set[SharedItem]: + children: Optional[list[SharedDirectory]] = None) -> set[SharedItem]: """Scans the directory for items to share Warning: when using ProcessPoolExecutor on this method the returned items @@ -160,8 +153,8 @@ def __init__( self._settings: Settings = settings self._event_bus: EventBus = event_bus self._network: Network = network - self._term_map: Dict[str, WeakSet[SharedItem]] = {} - self._shared_directories: List[SharedDirectory] = [] + self._term_map: dict[str, Weakset[SharedItem]] = {} + self._shared_directories: list[SharedDirectory] = [] self._session: Optional[Session] = None self.cache: SharesCache = cache if cache else SharesNullCache() @@ -223,7 +216,7 @@ async def start(self): if self.executor_factory: self.executor = self.executor_factory() - async def stop(self) -> List[asyncio.Task]: + async def stop(self) -> list[asyncio.Task]: if self.executor: self.executor.shutdown() self.executor = None @@ -242,7 +235,7 @@ def load_from_settings(self): will be updated, non-existing directories added, directories that no longer exist will be removed """ - new_shared_directories: List[SharedDirectory] = [] + new_shared_directories: list[SharedDirectory] = [] # Add or update directories for directory_entry in self._settings.shares.directories: @@ -345,7 +338,7 @@ async def get_shared_item(self, remote_path: str, username: Optional[str] = None def add_shared_directory( self, shared_directory: str, share_mode: DirectoryShareMode = DirectoryShareMode.EVERYONE, - users: Optional[List[str]] = None) -> SharedDirectory: + users: Optional[list[str]] = None) -> SharedDirectory: """Adds a shared directory. This method will call :meth:`generate_alias` and add the directory to the directory map. This method will not scan the directory, for scanning see the :meth:`scan`, @@ -390,7 +383,7 @@ def add_shared_directory( def update_shared_directory( self, directory: Union[str, SharedDirectory], share_mode: Optional[DirectoryShareMode] = None, - users: Optional[List[str]] = None) -> SharedDirectory: + users: Optional[list[str]] = None) -> SharedDirectory: """Updates `share_mode` and `users` values for the given directory :param directory: if a string is given this method will attempt to find @@ -507,7 +500,7 @@ async def scan_directory_files(self, shared_directory: SharedDirectory): logger.info("scheduling scan for directory : %r", shared_directory) try: - shared_items: Set[SharedItem] = await loop.run_in_executor( + shared_items: set[SharedItem] = await loop.run_in_executor( self.executor, partial( scan_directory, @@ -554,7 +547,7 @@ async def scan_directory_file_attributes(self, shared_directory: SharedDirectory loop = asyncio.get_running_loop() # Schedule the items on the executor - futures: List[asyncio.Future] = [] + futures: list[asyncio.Future] = [] for item in shared_directory.items: if item.attributes is None: future = loop.run_in_executor( @@ -621,7 +614,7 @@ async def get_filesize(self, shared_item: SharedItem) -> int: def query( self, query: Union[str, SearchQuery], username: Optional[str] = None, - excluded_search_phrases: Optional[List[str]] = None) -> Tuple[List[SharedItem], List[SharedItem]]: + excluded_search_phrases: Optional[list[str]] = None) -> tuple[list[SharedItem], list[SharedItem]]: """Performs a query on the ``shared_directories`` returning the matching items. If ``username`` is passed this method will return a list of visible results and list of locked results. If `None` the second list @@ -725,7 +718,7 @@ def query( else: return list(found_items), [] - def get_stats(self) -> Tuple[int, int]: + def get_stats(self) -> tuple[int, int]: """Gets the total amount of shared directories and files. :return: directory and file count as a ``tuple`` @@ -739,7 +732,7 @@ def get_stats(self) -> Tuple[int, int]: ) return dir_count, file_count - def calculate_download_path(self, remote_path: str) -> Tuple[str, str]: + def calculate_download_path(self, remote_path: str) -> tuple[str, str]: """Calculates the local download path for a remote path returned by another peer. @@ -753,7 +746,7 @@ def calculate_download_path(self, remote_path: str) -> Tuple[str, str]: download_dir ) - def get_shared_directories_for_user(self, username: str) -> Tuple[List[SharedDirectory], List[SharedDirectory]]: + def get_shared_directories_for_user(self, username: str) -> tuple[list[SharedDirectory], list[SharedDirectory]]: public_dirs = [] locked_dirs = [] for shared_dir in self._shared_directories: @@ -764,7 +757,7 @@ def get_shared_directories_for_user(self, username: str) -> Tuple[List[SharedDir return public_dirs, locked_dirs - def create_shares_reply(self, username: str) -> Tuple[List[DirectoryData], List[DirectoryData]]: + def create_shares_reply(self, username: str) -> tuple[list[DirectoryData], list[DirectoryData]]: """Creates a complete list of the currently shared items as a reply to a :class:`.PeerSharesRequest` messages @@ -773,8 +766,8 @@ def create_shares_reply(self, username: str) -> Tuple[List[DirectoryData], List[ :return: ``tuple`` with two lists: public directories and locked directories """ - def list_unique_directories(directories: List[SharedDirectory]) -> Dict[Tuple[str, ...], List[SharedItem]]: - response_dirs: Dict[Tuple[str, ...], List[SharedItem]] = {} + def list_unique_directories(directories: list[SharedDirectory]) -> dict[tuple[str, ...], list[SharedItem]]: + response_dirs: dict[tuple[str, ...], list[SharedItem]] = {} for directory in directories: for item in directory.items: @@ -790,7 +783,7 @@ def list_unique_directories(directories: List[SharedDirectory]) -> Dict[Tuple[st return response_dirs - def convert_to_directory_shares(directory_map: Dict[Tuple[str, ...], List[SharedItem]]) -> List[DirectoryData]: + def convert_to_directory_shares(directory_map: dict[tuple[str, ...], list[SharedItem]]) -> list[DirectoryData]: public_shares = [] for directory, files in directory_map.items(): public_shares.append( @@ -809,7 +802,7 @@ def convert_to_directory_shares(directory_map: Dict[Tuple[str, ...], List[Shared return visible_shares, locked_shares - def create_directory_reply(self, remote_directory: str) -> List[DirectoryData]: + def create_directory_reply(self, remote_directory: str) -> list[DirectoryData]: """Lists directory data as a response to a directory request. This will not contain any information about subdirectories, only the files within that directory @@ -822,7 +815,7 @@ def create_directory_reply(self, remote_directory: str) -> List[DirectoryData]: :return: list of directories. Empty if the directory is not shared, a list with one entry if the directory is found """ - items: List[SharedItem] = [] + items: list[SharedItem] = [] remote_dir_parts = tuple(remote_directory.split('\\')) remote_dir_parts_len = len(remote_dir_parts) @@ -865,7 +858,7 @@ def _cleanup_term_map(self): if len(values) > 0 } - def _get_parent_directories(self, shared_directory: SharedDirectory) -> List[SharedDirectory]: + def _get_parent_directories(self, shared_directory: SharedDirectory) -> list[SharedDirectory]: """Returns a list of parent shared directories. The parent directories will be sorted by length of the absolute path (longest last) """ diff --git a/src/aioslsk/shares/model.py b/src/aioslsk/shares/model.py index e225164d..1d19152a 100644 --- a/src/aioslsk/shares/model.py +++ b/src/aioslsk/shares/model.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field import enum import os -from typing import List, Optional, Set, Tuple, Union +from typing import Optional, Union from ..exceptions import FileNotFoundError from .utils import normalize_remote_path @@ -22,8 +22,8 @@ class SharedDirectory: absolute_path: str alias: str share_mode: DirectoryShareMode = field(default=DirectoryShareMode.EVERYONE, compare=False, hash=False) - users: List[str] = field(default_factory=list, compare=False, hash=False) - items: Set['SharedItem'] = field(default_factory=set, init=False, compare=False, hash=False, repr=False) + users: list[str] = field(default_factory=list, compare=False, hash=False) + items: set['SharedItem'] = field(default_factory=set, init=False, compare=False, hash=False, repr=False) def is_parent_of(self, directory: Union[str, 'SharedDirectory']) -> bool: """Returns true if the current directory is a parent of the passed @@ -58,7 +58,7 @@ def get_item_by_remote_path(self, remote_path: str) -> 'SharedItem': raise FileNotFoundError( f"file with remote path {remote_path!r} not found in directory {self!r}") - def get_items_for_directory(self, directory: 'SharedDirectory') -> Set['SharedItem']: + def get_items_for_directory(self, directory: 'SharedDirectory') -> set['SharedItem']: """Gets items that are part of given directory""" items = set() for item in self.items: @@ -73,7 +73,7 @@ class SharedItem: subdir: str filename: str modified: float - attributes: Optional[List[Tuple[int, int]]] = field( + attributes: Optional[list[tuple[int, int]]] = field( default=None, init=False, compare=False, @@ -101,7 +101,7 @@ def get_remote_directory_path(self) -> str: return normalize_remote_path( '@@' + os.path.join(self.shared_directory.alias, self.subdir)) - def get_remote_directory_path_parts(self) -> Tuple[str, ...]: + def get_remote_directory_path_parts(self) -> tuple[str, ...]: """Returns the remote directory path split into parts""" return tuple(self.get_remote_directory_path().split('\\')) diff --git a/src/aioslsk/shares/utils.py b/src/aioslsk/shares/utils.py index 77457e93..8cc3b342 100644 --- a/src/aioslsk/shares/utils.py +++ b/src/aioslsk/shares/utils.py @@ -2,7 +2,7 @@ import logging import os import re -from typing import List, Pattern, TYPE_CHECKING +from typing import TYPE_CHECKING from ..constants import PATH_SEPERATOR_PATTERN from ..protocol.primitives import Attribute, FileData @@ -22,7 +22,7 @@ def normalize_remote_path(path: str) -> str: return re.sub(PATH_SEPERATOR_PATTERN, '\\\\', path).rstrip('\\/') -def create_term_pattern(term: str, wildcard: bool = False) -> Pattern: +def create_term_pattern(term: str, wildcard: bool = False) -> re.Pattern: """Creates the matching pattern for a single search term of a query""" if wildcard: return re.compile( @@ -63,7 +63,7 @@ def convert_item_to_file_data( ) -def convert_items_to_file_data(shared_items: List[SharedItem], use_full_path=True) -> List[FileData]: +def convert_items_to_file_data(shared_items: list[SharedItem], use_full_path=True) -> list[FileData]: """Converts a list of :class:`.SharedItem` instances to a list of :class:`.FileData` instances. If an exception occurs when converting the item an error will be logged and the item will be omitted from the list diff --git a/src/aioslsk/tasks.py b/src/aioslsk/tasks.py index 80d15fd5..ff80e735 100644 --- a/src/aioslsk/tasks.py +++ b/src/aioslsk/tasks.py @@ -1,6 +1,7 @@ import asyncio +from collections.abc import Callable, Coroutine import logging -from typing import Any, Callable, Coroutine, Optional, Union +from typing import Any, Optional, Union logger = logging.getLogger(__name__) diff --git a/src/aioslsk/transfer/cache.py b/src/aioslsk/transfer/cache.py index 2a55cab2..d20053bc 100644 --- a/src/aioslsk/transfer/cache.py +++ b/src/aioslsk/transfer/cache.py @@ -2,7 +2,7 @@ import logging import os import shelve -from typing import List, Protocol +from typing import Protocol from .model import Transfer @@ -12,20 +12,20 @@ class TransferCache(Protocol): """Abstract base class for storing shares""" - def read(self) -> List['Transfer']: + def read(self) -> list['Transfer']: ... - def write(self, transfers: List['Transfer']): + def write(self, transfers: list['Transfer']): ... class TransferNullCache: """Transfer cache object that does not perform any caching""" - def read(self) -> List['Transfer']: # pragma: no cover + def read(self) -> list['Transfer']: # pragma: no cover return [] - def write(self, transfers: List['Transfer']): # pragma: no cover + def write(self, transfers: list['Transfer']): # pragma: no cover pass @@ -39,7 +39,7 @@ class TransferShelveCache: def __init__(self, data_directory: str): self.data_directory = data_directory - def read(self) -> List['Transfer']: + def read(self) -> list['Transfer']: db_path = os.path.join(self.data_directory, self.DEFAULT_FILENAME) transfers = [] @@ -51,7 +51,7 @@ def read(self) -> List['Transfer']: return transfers - def write(self, transfers: List['Transfer']): + def write(self, transfers: list['Transfer']): db_path = os.path.join(self.data_directory, self.DEFAULT_FILENAME) logger.info("writing %d transfers to : %s", len(transfers), db_path) diff --git a/src/aioslsk/transfer/manager.py b/src/aioslsk/transfer/manager.py index 40a26ae2..a4c9da5a 100644 --- a/src/aioslsk/transfer/manager.py +++ b/src/aioslsk/transfer/manager.py @@ -6,7 +6,7 @@ import logging from operator import itemgetter import os -from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from ..base_manager import BaseManager from .cache import TransferNullCache, TransferCache @@ -97,8 +97,8 @@ def __init__( self.cache: TransferCache = cache if cache else TransferNullCache() self._ticket_generator = ticket_generator() - self._transfers: List[Transfer] = [] - self._file_connection_futures: Dict[int, asyncio.Future] = {} + self._transfers: list[Transfer] = [] + self._file_connection_futures: dict[int, asyncio.Future] = {} self._progress_reporting_task: BackgroundTask = BackgroundTask( interval=self._settings.transfers.report_interval, task_coro=self._progress_reporting_job, @@ -125,7 +125,7 @@ async def read_cache(self): """Reads the transfers from the caches and corrects the state of those transfers """ - transfers: List[Transfer] = self.cache.read() + transfers: list[Transfer] = self.cache.read() for transfer in transfers: # Analyze the current state of the stored transfers and set them to # the correct state @@ -159,7 +159,7 @@ async def store_data(self): async def start(self): await self.start_progress_reporting_task() - async def stop(self) -> List[asyncio.Task]: + async def stop(self) -> list[asyncio.Task]: """Cancel all current transfer tasks :return: a list of tasks that have been cancelled so that they can be @@ -327,10 +327,10 @@ async def remove(self, transfer: Transfer): await self.manage_transfers() - def get_uploads(self) -> List[Transfer]: + def get_uploads(self) -> list[Transfer]: return [transfer for transfer in self._transfers if transfer.is_upload()] - def get_downloads(self) -> List[Transfer]: + def get_downloads(self) -> list[Transfer]: return [transfer for transfer in self._transfers if transfer.is_download()] def get_upload_slots(self) -> int: @@ -356,7 +356,7 @@ def get_queue_size(self) -> int: if transfer.is_upload() and transfer.state == TransferState.QUEUED ]) - def get_downloading(self) -> List[Transfer]: + def get_downloading(self) -> list[Transfer]: """Returns all transfers that are currently downloading or an attempt is is made to start downloading """ @@ -365,7 +365,7 @@ def get_downloading(self) -> List[Transfer]: if transfer.is_download() and transfer.is_processing() ] - def get_uploading(self) -> List[Transfer]: + def get_uploading(self) -> list[Transfer]: """Returns all transfers that are currently uploading or an attempt is is made to start uploading """ @@ -374,7 +374,7 @@ def get_uploading(self) -> List[Transfer]: if transfer.is_upload() and transfer.is_processing() ] - def get_finished_transfers(self) -> List[Transfer]: + def get_finished_transfers(self) -> list[Transfer]: """Returns a complete list of transfers that are in a finalized state (COMPLETE, ABORTED, FAILED) """ @@ -383,7 +383,7 @@ def get_finished_transfers(self) -> List[Transfer]: if transfer.is_finalized() ] - def get_unfinished_transfers(self) -> List[Transfer]: + def get_unfinished_transfers(self) -> list[Transfer]: """Returns a complete list of transfers that are not in a finalized state (COMPLETE, ABORTED, FAILED) """ @@ -493,20 +493,20 @@ async def manage_transfers(self): upload._transfer_task_complete ) - def _get_queued_transfers(self) -> Tuple[List[Transfer], List[Transfer]]: + def _get_queued_transfers(self) -> tuple[list[Transfer], list[Transfer]]: """Returns all transfers eligable for being initialized :return: a tuple containing 2 lists: the eligable downloads and eligable uploads """ - uploading_users: Set[str] = { + uploading_users: set[str] = { transfer.username for transfer in self._transfers if transfer.is_upload() and transfer.is_processing() } - users_with_queued_upload: Set[str] = set() + users_with_queued_upload: set[str] = set() - queued_downloads: List[Transfer] = [] - queued_uploads: List[Transfer] = [] + queued_downloads: list[Transfer] = [] + queued_uploads: list[Transfer] = [] for transfer in self._transfers: # Get the user object from the user manager, if the user is tracked # this user object will be returned. Otherwise a new user object is @@ -543,7 +543,7 @@ def _get_queued_transfers(self) -> Tuple[List[Transfer], List[Transfer]]: return queued_downloads, queued_uploads - def _prioritize_uploads(self, uploads: List[Transfer]) -> List[Transfer]: + def _prioritize_uploads(self, uploads: list[Transfer]) -> list[Transfer]: """Ranks the queued uploads by priority based on certain parameters :return: sorted list of provided by uploads by priority diff --git a/src/aioslsk/transfer/model.py b/src/aioslsk/transfer/model.py index 37f38a73..9873e1ab 100644 --- a/src/aioslsk/transfer/model.py +++ b/src/aioslsk/transfer/model.py @@ -5,7 +5,7 @@ from enum import Enum import logging import time -from typing import Deque, List, Optional, Tuple +from typing import Optional from .state import TransferState, TransferStateListener, VirginState @@ -96,11 +96,11 @@ def __init__(self, username: str, remote_path: str, direction: TransferDirection """Snapshot of the transfer progress. Used internally to report progress at set intervals """ - self._speed_log: Deque[Tuple[float, int]] = deque(maxlen=SPEED_LOG_ENTRIES) + self._speed_log: deque[tuple[float, int]] = deque(maxlen=SPEED_LOG_ENTRIES) self._remotely_queue_task: Optional[asyncio.Task] = None self._transfer_task: Optional[asyncio.Task] = None self._state_lock: asyncio.Lock = asyncio.Lock() - self.state_listeners: List[TransferStateListener] = [] + self.state_listeners: list[TransferStateListener] = [] def __setstate__(self, obj_state): """Called when unpickling""" @@ -279,7 +279,7 @@ def is_transferring(self) -> bool: def is_transfered(self) -> bool: return self.filesize == self.bytes_transfered - def get_tasks(self) -> List[asyncio.Task]: + def get_tasks(self) -> list[asyncio.Task]: tasks = [] if self._remotely_queue_task is not None: tasks.append(self._remotely_queue_task) @@ -287,7 +287,7 @@ def get_tasks(self) -> List[asyncio.Task]: tasks.append(self._transfer_task) return tasks - def cancel_tasks(self) -> List[asyncio.Task]: + def cancel_tasks(self) -> list[asyncio.Task]: """Cancels all tasks for the transfer, this method returns the tasks which have been cancelled """ diff --git a/src/aioslsk/user/manager.py b/src/aioslsk/user/manager.py index 01f1b68d..c4d1d037 100644 --- a/src/aioslsk/user/manager.py +++ b/src/aioslsk/user/manager.py @@ -2,7 +2,7 @@ import copy from dataclasses import dataclass import logging -from typing import Dict, Optional, Set +from typing import Optional from weakref import WeakValueDictionary from ..base_manager import BaseManager @@ -85,8 +85,8 @@ def __init__(self, settings: Settings, event_bus: EventBus, network: Network): self._MESSAGE_MAP = build_message_map(self) self._users: WeakValueDictionary[str, User] = WeakValueDictionary() - self._tracked_users: Dict[str, TrackedUser] = dict() - self._privileged_users: Set[str] = set() + self._tracked_users: dict[str, TrackedUser] = dict() + self._privileged_users: set[str] = set() self.register_listeners() @@ -101,11 +101,11 @@ def register_listeners(self): SessionDestroyedEvent, self._on_session_destroyed) @property - def users(self) -> Dict[str, User]: + def users(self) -> dict[str, User]: return dict(self._users) @property - def privileged_users(self) -> Set[str]: + def privileged_users(self) -> set[str]: return self._privileged_users def get_self(self) -> User: diff --git a/src/aioslsk/utils.py b/src/aioslsk/utils.py index 3d40d805..9b8c188f 100644 --- a/src/aioslsk/utils.py +++ b/src/aioslsk/utils.py @@ -1,7 +1,7 @@ +from collections.abc import Generator import itertools import logging import re -from typing import Generator, List from .constants import PATH_SEPERATOR_PATTERN from .protocol.primitives import Attribute @@ -24,12 +24,12 @@ def try_decoding(value: bytes): raise -def split_remote_path(path: str) -> List[str]: +def split_remote_path(path: str) -> list[str]: """Splits a remote path into parts. Empty parts will be filtered out""" return [part for part in re.split(PATH_SEPERATOR_PATTERN, path) if part] -def get_duration(attributes: List[Attribute]) -> str: +def get_duration(attributes: list[Attribute]) -> str: duration = '' for attr in attributes: if attr.key == 1: @@ -40,7 +40,7 @@ def get_duration(attributes: List[Attribute]) -> str: return duration -def get_attribute_string(attributes: List[Attribute]) -> str: +def get_attribute_string(attributes: list[Attribute]) -> str: attr_str = [] for attr in attributes: if attr.key == 0: diff --git a/tests/e2e/fixtures.py b/tests/e2e/fixtures.py index 7919a130..3b7b450c 100644 --- a/tests/e2e/fixtures.py +++ b/tests/e2e/fixtures.py @@ -1,10 +1,10 @@ import asyncio from async_timeout import timeout as atimeout +from collections.abc import AsyncGenerator import os from pathlib import Path import pytest_asyncio import shutil -from typing import AsyncGenerator, List from aioslsk.client import SoulSeekClient from aioslsk.events import SessionInitializedEvent @@ -132,8 +132,8 @@ async def client_2(tmp_path: Path) -> AsyncGenerator[SoulSeekClient, None]: @pytest_asyncio.fixture -async def clients(tmp_path: Path, request) -> AsyncGenerator[List[SoulSeekClient], None]: - clients: List[SoulSeekClient] = [] +async def clients(tmp_path: Path, request) -> AsyncGenerator[list[SoulSeekClient], None]: + clients: list[SoulSeekClient] = [] for idx in range(request.param): username = 'user' + str(idx).zfill(3) diff --git a/tests/e2e/mock/constants.py b/tests/e2e/mock/constants.py index fde88ce2..672330d8 100644 --- a/tests/e2e/mock/constants.py +++ b/tests/e2e/mock/constants.py @@ -1,8 +1,7 @@ import struct -from typing import List -DEFAULT_EXCLUDED_SEARCH_PHRASES: List[str] = ['banned phrase'] +DEFAULT_EXCLUDED_SEARCH_PHRASES: list[str] = ['banned phrase'] HEADER_SIZE: int = struct.calcsize('I') MAX_ITEM_RECOMMENDATIONS: int = 100 """Max items returned by GetItemRecommendations per list (value is known)""" diff --git a/tests/e2e/mock/distributed.py b/tests/e2e/mock/distributed.py index 947a33c4..3bef2244 100644 --- a/tests/e2e/mock/distributed.py +++ b/tests/e2e/mock/distributed.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod from math import ceil import random -from typing import Dict, List from tests.e2e.mock.peer import Peer from tests.e2e.mock.model import DistributedValues, Settings @@ -9,12 +8,12 @@ class DistributedStrategy(ABC): def __init__( - self, settings: Settings, peers: List[Peer], - distributed_tree: Dict[str, DistributedValues]): + self, settings: Settings, peers: list[Peer], + distributed_tree: dict[str, DistributedValues]): self.settings: Settings = settings - self.peers: List[Peer] = peers - self.tree: Dict[str, DistributedValues] = distributed_tree + self.peers: list[Peer] = peers + self.tree: dict[str, DistributedValues] = distributed_tree @classmethod @abstractmethod @@ -22,23 +21,23 @@ def get_name(cls) -> str: ... @abstractmethod - def get_potential_parents(self, target_peer: Peer) -> List[Peer]: + def get_potential_parents(self, target_peer: Peer) -> list[Peer]: ... - def get_roots(self) -> List[Peer]: + def get_roots(self) -> list[Peer]: """Returns the distributed roots""" return self.get_potential_roots() - def get_potential_roots(self) -> List[Peer]: + def get_potential_roots(self) -> list[Peer]: return [ peer for peer in self._get_valid_peers() if self.tree[peer.user.name].level == 0 ] - def _get_valid_peers(self) -> List[Peer]: + def _get_valid_peers(self) -> list[Peer]: return [peer for peer in self.peers if peer.user] - def _get_peers_accepting_children(self) -> List[Peer]: + def _get_peers_accepting_children(self) -> list[Peer]: """Returns a list of all peers that are: * Have a valid user assigned @@ -54,7 +53,7 @@ def _get_peers_accepting_children(self) -> List[Peer]: return eligable_peers - def sort_peers_by_connect_time(self, peers: List[Peer], reverse: bool = False) -> List[Peer]: + def sort_peers_by_connect_time(self, peers: list[Peer], reverse: bool = False) -> list[Peer]: peers.sort( key=lambda p: p.connect_time, reverse=reverse @@ -69,13 +68,13 @@ class ConnectTimeOldestRootStrategy(DistributedStrategy): def get_name(cls) -> str: return 'connect_time_oldest_root' - def get_potential_parents(self, target_peer: Peer) -> List[Peer]: + def get_potential_parents(self, target_peer: Peer) -> list[Peer]: return [ peer for peer in self.get_roots() if peer != target_peer ] - def get_roots(self) -> List[Peer]: + def get_roots(self) -> list[Peer]: peers = self.sort_peers_by_connect_time(self.get_potential_roots()) if not peers: return [] @@ -92,7 +91,7 @@ class ConnectTimeChainStrategy(DistributedStrategy): def get_name(cls) -> str: return 'connect_time_chain' - def get_potential_parents(self, target_peer: Peer) -> List[Peer]: + def get_potential_parents(self, target_peer: Peer) -> list[Peer]: peers = [ peer for peer in self._get_peers_accepting_children() if peer != target_peer and peer.connect_time < target_peer.connect_time @@ -103,7 +102,7 @@ def get_potential_parents(self, target_peer: Peer) -> List[Peer]: return self.sort_peers_by_connect_time(peers, reverse=True)[:1] - def get_roots(self) -> List[Peer]: + def get_roots(self) -> list[Peer]: peers = self.get_potential_roots() if not peers: return [] @@ -120,7 +119,7 @@ class ChainParentsStrategy(DistributedStrategy): def get_name(cls) -> str: return 'chain' - def get_potential_parents(self, target_peer: Peer) -> List[Peer]: + def get_potential_parents(self, target_peer: Peer) -> list[Peer]: accepting_peers = self._get_peers_accepting_children() try: max_level = max([ @@ -143,10 +142,10 @@ class EveryoneRootStrategy(DistributedStrategy): def get_name(cls) -> str: return 'everyone_root' - def get_roots(self) -> List[Peer]: + def get_roots(self) -> list[Peer]: return self.peers - def get_potential_parents(self, target_peer: Peer) -> List[Peer]: + def get_potential_parents(self, target_peer: Peer) -> list[Peer]: return [] @@ -209,7 +208,7 @@ class RealisticParentsStrategy(DistributedStrategy): selection process. """ - def __init__(self, settings: Settings, peers: List[Peer]): + def __init__(self, settings: Settings, peers: list[Peer]): super().__init__(settings, peers) self.root_percentage: float = 0.01 @@ -217,7 +216,7 @@ def __init__(self, settings: Settings, peers: List[Peer]): def get_name(cls) -> str: return 'realistic' - def get_potential_parents(self, target_peer: Peer) -> List[Peer]: + def get_potential_parents(self, target_peer: Peer) -> list[Peer]: possible_peers = [] for peer in self._get_peers_accepting_children(): if peer == target_peer: @@ -228,7 +227,7 @@ def get_potential_parents(self, target_peer: Peer) -> List[Peer]: return random.choices(possible_peers, k=10) - def get_roots(self) -> List[Peer]: + def get_roots(self) -> list[Peer]: roots = sorted( self.get_potential_roots(), key=lambda p: p.user.avg_speed, diff --git a/tests/e2e/mock/model.py b/tests/e2e/mock/model.py index 067bd744..4f7269af 100644 --- a/tests/e2e/mock/model.py +++ b/tests/e2e/mock/model.py @@ -4,7 +4,7 @@ from aioslsk.protocol.messages import PrivateChatMessage from aioslsk.user.model import UserStatus import typing -from typing import Dict, List, Set, Optional +from typing import Optional from weakref import WeakValueDictionary @@ -86,11 +86,11 @@ class User: port: Optional[int] = None obfuscated_port: Optional[int] = None - interests: Set[str] = field(default_factory=set) - hated_interests: Set[str] = field(default_factory=set) + interests: set[str] = field(default_factory=set) + hated_interests: set[str] = field(default_factory=set) - queued_private_messages: List[QueuedPrivateMessage] = field(default_factory=list) - added_users: Dict[str, 'User'] = field(default_factory=WeakValueDictionary) + queued_private_messages: list[QueuedPrivateMessage] = field(default_factory=list) + added_users: dict[str, 'User'] = field(default_factory=WeakValueDictionary) # TODO: Investigate what the default values are enable_private_rooms: bool = False @@ -129,14 +129,14 @@ class RoomStatus(Enum): @dataclass class Room: name: str - joined_users: List[User] = field(default_factory=list) + joined_users: list[User] = field(default_factory=list) tickers: typing.OrderedDict[str, str] = field(default_factory=OrderedDict) registered_as_public: bool = False # Only for private rooms owner: Optional[User] = None - members: List[User] = field(default_factory=list) - operators: List[User] = field(default_factory=list) + members: list[User] = field(default_factory=list) + operators: list[User] = field(default_factory=list) @property def status(self) -> RoomStatus: @@ -148,7 +148,7 @@ def status(self) -> RoomStatus: return RoomStatus.UNCLAIMED @property - def all_members(self) -> List[User]: + def all_members(self) -> list[User]: return self.members + [self.owner, ] if self.owner else [] def can_join(self, user: User) -> bool: diff --git a/tests/e2e/mock/server.py b/tests/e2e/mock/server.py index 27919797..bff8e1db 100644 --- a/tests/e2e/mock/server.py +++ b/tests/e2e/mock/server.py @@ -1,14 +1,14 @@ import argparse import asyncio from collections import Counter +from collections.abc import Generator from dataclasses import fields from functools import partial import logging import re import socket import time -from typing import Dict, Generator, List, Optional, Set, Type, TypeVar -import typing +from typing import Optional, TypeVar from aioslsk.events import build_message_map from aioslsk.user.model import UserStatus @@ -137,7 +137,7 @@ def ticket_generator(initial: int = 1) -> Generator[int, None, None]: yield idx -def remove_0_values(counter: typing.Counter[str]): +def remove_0_values(counter: Counter[str]): recommendations_to_remove = [ key for key, value in counter.items() if value == 0 ] @@ -145,7 +145,7 @@ def remove_0_values(counter: typing.Counter[str]): del counter[to_remove] -def on_message(message_class: Type[MessageDataclass], require_user: bool = True): +def on_message(message_class: type[MessageDataclass], require_user: bool = True): """Decorator for methods listening to specific `MessageData` events """ def register(event_func): @@ -159,24 +159,24 @@ def register(event_func): class MockServer: def __init__( - self, hostname: str = '0.0.0.0', ports: Set[int] = {2416}, - excluded_search_phrases: List[str] = DEFAULT_EXCLUDED_SEARCH_PHRASES, + self, hostname: str = '0.0.0.0', ports: set[int] = {2416}, + excluded_search_phrases: list[str] = DEFAULT_EXCLUDED_SEARCH_PHRASES, potential_parent_interval: int = 0, - distributed_strategy_class: Type[DistributedStrategy] = EveryoneRootStrategy, + distributed_strategy_class: type[DistributedStrategy] = EveryoneRootStrategy, settings: Optional[Settings] = None, mock_variables: Optional[MockVariables] = None): self.hostname: str = hostname - self.ports: Set[int] = ports - self.connections: Dict[int, asyncio.Server] = {} + self.ports: set[int] = ports + self.connections: dict[int, asyncio.Server] = {} self.settings: Settings = settings or Settings() - self.users: List[User] = [] - self.rooms: List[Room] = [] - self.peers: List[Peer] = [] - self.distributed_tree: Dict[str, DistributedValues] = {} + self.users: list[User] = [] + self.rooms: list[Room] = [] + self.peers: list[Peer] = [] + self.distributed_tree: dict[str, DistributedValues] = {} - self.excluded_search_phrases: List[str] = excluded_search_phrases + self.excluded_search_phrases: list[str] = excluded_search_phrases self.distributed_strategy: DistributedStrategy = self._create_distributed_strategy(distributed_strategy_class) self.mock_variables: MockVariables = mock_variables or MockVariables() @@ -194,10 +194,10 @@ def __init__( self.periodic_search_task = asyncio.create_task( self.search_request_job(interval=self.mock_variables.search_interval)) - def _create_distributed_strategy(self, strategy_cls: Type[T]) -> T: + def _create_distributed_strategy(self, strategy_cls: type[T]) -> T: return strategy_cls(self.settings, self.peers, self.distributed_tree) - def set_distributed_strategy(self, strategy_cls: Type[DistributedStrategy]): + def set_distributed_strategy(self, strategy_cls: type[DistributedStrategy]): self.distributed_strategy = self._create_distributed_strategy(strategy_cls) async def notify_potential_parents(self): @@ -335,7 +335,7 @@ async def on_peer_message(self, message, peer: Peer): await method(message, peer) - def get_valid_peers(self) -> List[Peer]: + def get_valid_peers(self) -> list[Peer]: """Returns all peers which are logged in (user set)""" return [peer for peer in self.peers if peer.user] @@ -354,10 +354,10 @@ def find_room_by_name(self, name: str) -> Optional[Room]: if room.name == name: return room - def get_user_private_rooms(self, user: User) -> List[Room]: + def get_user_private_rooms(self, user: User) -> list[Room]: return [room for room in self.rooms if user in room.all_members] - def get_joined_rooms(self, user: User) -> List[Room]: + def get_joined_rooms(self, user: User) -> list[Room]: return [room for room in self.rooms if user in room.joined_users] async def set_upload_speed(self, username: str, uploads: int, speed: int): @@ -403,7 +403,7 @@ async def send_search_request(self, username: str, sender: str, query: str, tick await peer.send_message(message) async def send_potential_parents( - self, username: str, potential_parents: List[str] = None): + self, username: str, potential_parents: list[str] = None): """This is a utility method used for testing to send the potential parents to the peer with given username @@ -439,7 +439,7 @@ async def send_potential_parents( await peer.send_message(message) async def send_private_message( - self, message: str, sender: str, receivers: List[str]): + self, message: str, sender: str, receivers: list[str]): """Sends a private message to one or more users""" queued_message = QueuedPrivateMessage( @@ -472,10 +472,10 @@ async def send_queued_private_messages(self, peer: Peer): queued_message.to_protocol_message(is_direct=False)) def _create_room_list_message(self, user: User, min_users: int = 1): - public_rooms: List[Room] = [] - private_rooms: List[Room] = [] - private_rooms_owned: List[Room] = [] - private_rooms_operated: List[Room] = [] + public_rooms: list[Room] = [] + private_rooms: list[Room] = [] + private_rooms_owned: list[Room] = [] + private_rooms_operated: list[Room] = [] for room in self.rooms: if room.status == RoomStatus.PRIVATE: if room.owner == user: @@ -1110,7 +1110,7 @@ async def on_get_item_recommendations(self, message: GetItemRecommendations.Requ rec_counter = self.get_recommendations_for_item(message.item) del rec_counter[message.item] - recommendations: List[Recommendation] = [] + recommendations: list[Recommendation] = [] for rec, score in rec_counter.most_common(MAX_RECOMMENDATIONS): recommendations.append(Recommendation(rec, score)) @@ -1849,7 +1849,7 @@ async def revoke_operator(self, room: Room, user: User): AdminMessage.PRIVATE_ROOM_OPERATOR_REVOKED.format(user.name, room.name) ) - def get_global_recommendations(self) -> typing.Counter[str]: + def get_global_recommendations(self) -> Counter[str]: """Gets global recommendations :return: List of global recommendations for this item. This still needs @@ -1865,7 +1865,7 @@ def get_global_recommendations(self) -> typing.Counter[str]: return recommendations - def get_recommendations_for_item(self, item: str) -> typing.Counter[str]: + def get_recommendations_for_item(self, item: str) -> Counter[str]: """Gets item recommendations :return: List of recommendations for this item. This still includes @@ -1882,7 +1882,7 @@ def get_recommendations_for_item(self, item: str) -> typing.Counter[str]: return recommendations - def get_recommendations_for_user(self, user: User) -> typing.Counter[str]: + def get_recommendations_for_user(self, user: User) -> Counter[str]: """Gets all recommendations of a user based on his interests""" counter = Counter() for other_peer in self.get_valid_peers(): @@ -1912,7 +1912,7 @@ def get_recommendations_for_user(self, user: User) -> typing.Counter[str]: return counter -def _get_distributed_strategy_class(name: str) -> Type[DistributedStrategy]: +def _get_distributed_strategy_class(name: str) -> type[DistributedStrategy]: for strategy_cls in DistributedStrategy.__subclasses__(): if strategy_cls.get_name() == name: return strategy_cls @@ -1921,7 +1921,7 @@ def _get_distributed_strategy_class(name: str) -> Type[DistributedStrategy]: def _create_argument_group( - parser: argparse.ArgumentParser, dataclass_cls: Type[S], + parser: argparse.ArgumentParser, dataclass_cls: type[S], prefix: Optional[str] = None): """Creates an argparse argument group for a dataclass @@ -1945,7 +1945,7 @@ def _create_argument_group( ) -def _parse_argument_group(args, dataclass_cls: Type[S], prefix: Optional[str] = None) -> S: +def _parse_argument_group(args, dataclass_cls: type[S], prefix: Optional[str] = None) -> S: """Parses an argument group into a dataclass""" arg_prefix = f'{prefix}_' if prefix else '' diff --git a/tests/e2e/test_e2e_distributed.py b/tests/e2e/test_e2e_distributed.py index 98016ea5..b45c45fb 100644 --- a/tests/e2e/test_e2e_distributed.py +++ b/tests/e2e/test_e2e_distributed.py @@ -10,7 +10,6 @@ ) import asyncio import pytest -from typing import List async def set_upload_speed_for_client(mock_server: MockServer, client: SoulSeekClient, value: int = 10000): @@ -18,7 +17,7 @@ async def set_upload_speed_for_client(mock_server: MockServer, client: SoulSeekC await mock_server.set_upload_speed(username, uploads=10, speed=value) -async def set_upload_speed_for_clients(mock_server: MockServer, clients: List[SoulSeekClient], value: int = 10000): +async def set_upload_speed_for_clients(mock_server: MockServer, clients: list[SoulSeekClient], value: int = 10000): """Sets a dummy upload speed on the mock server for all clients. This is necessary in order for the clients to accept children """ @@ -32,7 +31,7 @@ class TestE2EDistributed: @pytest.mark.asyncio @pytest.mark.parametrize("clients", [2], indirect=True) - async def test_root_user(self, mock_server: MockServer, clients: List[SoulSeekClient]): + async def test_root_user(self, mock_server: MockServer, clients: list[SoulSeekClient]): """Tests when a user gets a search request directly from the server the peer becomes root """ @@ -60,7 +59,7 @@ async def test_root_user(self, mock_server: MockServer, clients: List[SoulSeekCl @pytest.mark.asyncio @pytest.mark.parametrize("clients", [2], indirect=True) - async def test_level1_user(self, mock_server: MockServer, clients: List[SoulSeekClient]): + async def test_level1_user(self, mock_server: MockServer, clients: list[SoulSeekClient]): """Tests when a user gets a search request directly from the server the peer becomes root """ @@ -100,7 +99,7 @@ async def test_level1_user(self, mock_server: MockServer, clients: List[SoulSeek @pytest.mark.asyncio @pytest.mark.parametrize("clients", [3], indirect=True) - async def test_level2_user(self, mock_server: MockServer, clients: List[SoulSeekClient]): + async def test_level2_user(self, mock_server: MockServer, clients: list[SoulSeekClient]): """Tests when a user gets a search request directly from the server the peer becomes root """ @@ -167,7 +166,7 @@ async def test_level2_user(self, mock_server: MockServer, clients: List[SoulSeek @pytest.mark.asyncio @pytest.mark.parametrize("clients", [4], indirect=True) - async def test_level2_sendSearchRequest(self, mock_server: MockServer, clients: List[SoulSeekClient]): + async def test_level2_sendSearchRequest(self, mock_server: MockServer, clients: list[SoulSeekClient]): """Tests if clients on multiple levels in the network receive a search request """ diff --git a/tests/unit/network/test_network.py b/tests/unit/network/test_network.py index 03af08de..845c98f5 100644 --- a/tests/unit/network/test_network.py +++ b/tests/unit/network/test_network.py @@ -1,6 +1,6 @@ import pytest -from typing import Tuple from unittest.mock import AsyncMock, Mock +from typing import Optional from aioslsk.events import EventBus from aioslsk.exceptions import ConnectionFailedError, ListeningConnectionFailedError @@ -39,7 +39,7 @@ class TestNetworkManager: - def _create_network(self, settings=None) -> Network: + def _create_network(self, settings: Optional[Settings] = None) -> Network: sett = settings or Settings(**DEFAULT_SETTINGS) network = Network(sett, EventBus()) network.server_connection = Mock() @@ -58,7 +58,7 @@ def _create_network(self, settings=None) -> Network: (1, 2, True, (2, True)), ] ) - def test_selectPort(self, clear_port: int, obfuscated_port: int, prefer_obfuscated: bool, expected: Tuple[int, bool]): + def test_selectPort(self, clear_port: int, obfuscated_port: int, prefer_obfuscated: bool, expected: tuple[int, bool]): network = self._create_network() network._settings.network.peer.obfuscate = prefer_obfuscated actual = network.select_port(clear_port, obfuscated_port) diff --git a/tests/unit/shares/test_shares_manager.py b/tests/unit/shares/test_shares_manager.py index 2957562f..95611ee0 100644 --- a/tests/unit/shares/test_shares_manager.py +++ b/tests/unit/shares/test_shares_manager.py @@ -15,7 +15,7 @@ from pytest_unordered import unordered import os import sys -from typing import Optional, List, Type +from typing import Optional from unittest.mock import patch, AsyncMock @@ -163,7 +163,7 @@ class TestSharesManagerQuery: ('片仮名', ['item2', 'item3']) ] ) - def test_querySimpleTerms_matching(self, manager_query: SharesManager, query: str, expected_items: List[str]): + def test_querySimpleTerms_matching(self, manager_query: SharesManager, query: str, expected_items: list[str]): expected_items = [SHARED_ITEMS[item_name] for item_name in expected_items] actual_items, locked_items = manager_query.query(query) assert expected_items == unordered(actual_items) @@ -200,7 +200,7 @@ def test_querySimpleTerms_notMatching(self, manager_query: SharesManager, query: ('simple_band', ['item3']), ] ) - def test_querySpecialCharactersInTerm_matching(self, manager_query: SharesManager, query: str, expected_items: List[str]): + def test_querySpecialCharactersInTerm_matching(self, manager_query: SharesManager, query: str, expected_items: list[str]): expected_items = [SHARED_ITEMS[item_name] for item_name in expected_items] actual_items, locked_items = manager_query.query(query) assert expected_items == unordered(actual_items) @@ -221,7 +221,7 @@ def test_querySpecialCharactersInTerm_matching(self, manager_query: SharesManage ('*仮名', ['item2', 'item3']), ] ) - def test_queryWildcard_matching(self, manager_query: SharesManager, query: str, expected_items: List[str]): + def test_queryWildcard_matching(self, manager_query: SharesManager, query: str, expected_items: list[str]): expected_items = [SHARED_ITEMS[item_name] for item_name in expected_items] actual_items, locked_items = manager_query.query(query) assert expected_items == unordered(actual_items) @@ -240,7 +240,7 @@ def test_queryWildcard_matching(self, manager_query: SharesManager, query: str, ('simple -片仮名', ['item1']) ] ) - def test_queryExcludeTerm(self, manager_query: SharesManager, query: str, expected_items: List[str]): + def test_queryExcludeTerm(self, manager_query: SharesManager, query: str, expected_items: list[str]): expected_items = [SHARED_ITEMS[item_name] for item_name in expected_items] actual_items, locked_items = manager_query.query(query) assert expected_items == unordered(actual_items) @@ -266,7 +266,7 @@ def test_queryExcludedSearchPhrases(self, manager_query: SharesManager): ('simple -singer)_-_don\'t', ['item1', 'item3']), ] ) - def test_edgeCases(self, manager_query: SharesManager, query: str, expected_items: List[str]): + def test_edgeCases(self, manager_query: SharesManager, query: str, expected_items: list[str]): expected_items = [SHARED_ITEMS[item_name] for item_name in expected_items] actual_items, locked_items = manager_query.query(query) assert expected_items == unordered(actual_items) @@ -289,7 +289,7 @@ def test_edgeCases(self, manager_query: SharesManager, query: str, expected_item ('song -folk/folkalbum', ['item1', 'item2', 'item3']), ] ) - def test_queryTermsWithSlashes(self, manager_query: SharesManager, query: str, expected_items: List[str]): + def test_queryTermsWithSlashes(self, manager_query: SharesManager, query: str, expected_items: list[str]): expected_items = [SHARED_ITEMS[item_name] for item_name in expected_items] actual_items, locked_items = manager_query.query(query) assert expected_items == unordered(actual_items) @@ -326,7 +326,7 @@ def test_loadFromSettings(self, manager: SharesManager): ProcessPoolExecutor ] ) - async def test_scan(self, manager: SharesManager, executor: Optional[Type[Executor]]): + async def test_scan(self, manager: SharesManager, executor: Optional[type[Executor]]): manager.load_from_settings() manager.executor = executor() if executor else None diff --git a/tests/unit/transfer/test_transfer_cache.py b/tests/unit/transfer/test_transfer_cache.py index e921bd63..bb8b5adf 100644 --- a/tests/unit/transfer/test_transfer_cache.py +++ b/tests/unit/transfer/test_transfer_cache.py @@ -1,7 +1,6 @@ import copy import os from pytest_unordered import unordered -from typing import List import shutil from aioslsk.transfer.cache import TransferShelveCache @@ -16,7 +15,7 @@ RESOURCES = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'resources') -def create_transfers() -> List[Transfer]: +def create_transfers() -> list[Transfer]: download = Transfer('user0', '@abcdef\\file.mp3', TransferDirection.DOWNLOAD) download.state = CompleteState(download) download.start_time = 1.0 diff --git a/tests/unit/transfer/test_transfer_state.py b/tests/unit/transfer/test_transfer_state.py index 1b6fa9b0..656ef4ef 100644 --- a/tests/unit/transfer/test_transfer_state.py +++ b/tests/unit/transfer/test_transfer_state.py @@ -1,4 +1,3 @@ -from typing import Type from unittest.mock import MagicMock, patch import pytest @@ -44,7 +43,7 @@ async def test_whenTransitionVirginToQueued_shouldResetRemotelyQueued(self): UploadingState, ]) @pytest.mark.asyncio - async def test_whenTransitionToFailed_shouldSetStateAndCompleteTime(self, initial_state: Type[TransferState]): + async def test_whenTransitionToFailed_shouldSetStateAndCompleteTime(self, initial_state: type[TransferState]): time_mock = MagicMock(return_value=12.0) transfer = Transfer(None, None, TransferDirection.DOWNLOAD) @@ -63,7 +62,7 @@ async def test_whenTransitionToFailed_shouldSetStateAndCompleteTime(self, initia IncompleteState, ]) @pytest.mark.asyncio - async def test_whenTransitionToFailed_shouldSetState(self, initial_state: Type[TransferState]): + async def test_whenTransitionToFailed_shouldSetState(self, initial_state: type[TransferState]): transfer = Transfer(None, None, TransferDirection.DOWNLOAD) transfer.state = initial_state(transfer) await transfer.state.fail(reason='err') @@ -77,7 +76,7 @@ async def test_whenTransitionToFailed_shouldSetState(self, initial_state: Type[T UploadingState, ]) @pytest.mark.asyncio - async def test_whenTransitionToAborted_shouldSetStateAndCompleteTime(self, initial_state: Type[TransferState]): + async def test_whenTransitionToAborted_shouldSetStateAndCompleteTime(self, initial_state: type[TransferState]): time_mock = MagicMock(return_value=12.0) transfer = Transfer(None, None, TransferDirection.DOWNLOAD) @@ -95,7 +94,7 @@ async def test_whenTransitionToAborted_shouldSetStateAndCompleteTime(self, initi IncompleteState, ]) @pytest.mark.asyncio - async def test_whenTransitionToAborted_shouldSetState(self, initial_state: Type[TransferState]): + async def test_whenTransitionToAborted_shouldSetState(self, initial_state: type[TransferState]): transfer = Transfer(None, None, TransferDirection.DOWNLOAD) transfer.state = initial_state(transfer) await transfer.state.abort() @@ -107,7 +106,7 @@ async def test_whenTransitionToAborted_shouldSetState(self, initial_state: Type[ InitializingState, ]) @pytest.mark.asyncio - async def test_whenTransitionToQueued_shouldSetState(self, initial_state: Type[TransferState]): + async def test_whenTransitionToQueued_shouldSetState(self, initial_state: type[TransferState]): transfer = Transfer(None, None, TransferDirection.DOWNLOAD) transfer.state = initial_state(transfer) await transfer.state.queue() @@ -121,7 +120,7 @@ async def test_whenTransitionToQueued_shouldSetState(self, initial_state: Type[T FailedState, ]) @pytest.mark.asyncio - async def test_whenTransitionToQueued_shouldSetStateAndResetTimes(self, initial_state: Type[TransferState]): + async def test_whenTransitionToQueued_shouldSetStateAndResetTimes(self, initial_state: type[TransferState]): transfer = Transfer(None, None, TransferDirection.DOWNLOAD) transfer.state = initial_state(transfer) transfer.start_time = 1.0 From e2a1681a74c31e6d5cd82b8d9ead5a087b90ecf1 Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sun, 8 Sep 2024 18:12:44 +0200 Subject: [PATCH 5/7] deps: fix typing broken due to replacement --- src/aioslsk/shares/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aioslsk/shares/manager.py b/src/aioslsk/shares/manager.py index e3c5df4d..ea24cf1f 100644 --- a/src/aioslsk/shares/manager.py +++ b/src/aioslsk/shares/manager.py @@ -153,7 +153,7 @@ def __init__( self._settings: Settings = settings self._event_bus: EventBus = event_bus self._network: Network = network - self._term_map: dict[str, Weakset[SharedItem]] = {} + self._term_map: dict[str, WeakSet[SharedItem]] = {} self._shared_directories: list[SharedDirectory] = [] self._session: Optional[Session] = None From 72bbfd3c4a124261e0ab62cfce6bf663351ac1de Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:56:43 +0200 Subject: [PATCH 6/7] deps: remove using collections from typing --- src/aioslsk/user/model.py | 6 +++--- tests/e2e/test_e2e_search.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/aioslsk/user/model.py b/src/aioslsk/user/model.py index f9f91d3d..b8560fae 100644 --- a/src/aioslsk/user/model.py +++ b/src/aioslsk/user/model.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field from enum import auto, Enum, Flag -from typing import Optional, Set +from typing import Optional from ..protocol.primitives import UserStats @@ -65,8 +65,8 @@ class User: upload_permissions: UploadPermissions = UploadPermissions.UNKNOWN # Interests - interests: Set[str] = field(default_factory=set) - hated_interests: Set[str] = field(default_factory=set) + interests: set[str] = field(default_factory=set) + hated_interests: set[str] = field(default_factory=set) def update_from_user_stats(self, user_stats: UserStats): self.avg_speed = user_stats.avg_speed diff --git a/tests/e2e/test_e2e_search.py b/tests/e2e/test_e2e_search.py index c130793b..96a3cd76 100644 --- a/tests/e2e/test_e2e_search.py +++ b/tests/e2e/test_e2e_search.py @@ -14,7 +14,6 @@ wait_for_listener_awaited_events, ) import pytest -from typing import Tuple from unittest.mock import AsyncMock @@ -57,7 +56,7 @@ async def test_search_without_timeout( ) async def test_search_with_timeout( self, mock_server: MockServer, client_1: SoulSeekClient, - search_func_name: str, search_params: Tuple, search_type: SearchType): + search_func_name: str, search_params: tuple, search_type: SearchType): await wait_until_clients_initialized(mock_server, amount=1) From f60865b2e551a9fd97f9712054c1ebdc7d12b4bf Mon Sep 17 00:00:00 2001 From: JurgenR <1249228+JurgenR@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:57:35 +0200 Subject: [PATCH 7/7] deps: drop rc part for Python 3.13 --- .github/workflows/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 64f621ab..07492f5c 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -31,7 +31,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13.0-rc.1"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] exclude: - os: macos-latest python-version: "3.10"