diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2852d922d..0f07c67e5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -66,8 +66,8 @@ If you don't, our test suite will fail because we use Git tags for packaging. Finally, **clone** your fork using one of the alternatives that you can copy-paste by pressing the big green button labeled `<> Code`. -You can (and should) run our test suite using [*tox*](https://tox.wiki/) with the [*tox-uv*](https://github.com/tox-dev/tox-uv) plugin. -The easiest way is to [install *uv*] which is needed in any case and then run `uv tool install --with tox-uv tox` to have it globally available or `uvx --with tox-uv tox` to use a temporary environment. +You can (and should) run our full test suite using [*tox*](https://tox.wiki/). +It's part of our development environment, so `uv run tox` works out of the box once you've [installed *uv*][install *uv*]. --- @@ -118,7 +118,7 @@ $ git fetch upstream --tags When working on the documentation, use: ```console -$ tox run -e docs-watch +$ uv run tox run -e docs-watch ``` This will build the documentation, watch for changes, and rebuild it whenever you save a file. @@ -126,7 +126,7 @@ This will build the documentation, watch for changes, and rebuild it whenever yo To just build the documentation and exit immediately use: ```console -$ tox run -e docs-build +$ uv run tox run -e docs-build ``` You will find the built documentation in `docs/_build/html`. @@ -134,7 +134,7 @@ You will find the built documentation in `docs/_build/html`. To run doctests: ```console -$ tox run -e docs-doctests +$ uv run tox run -e docs-doctests ``` @@ -159,7 +159,7 @@ $ tox run -e docs-doctests assert "foo" == x._a_private_attribute ``` -- You can run the test suite with all dependencies against all supported Python versions - just as it will in our CI - by running `tox`. +- You can run the test suite with all dependencies against all supported Python versions - just as it will in our CI - by running `uv run tox`. - Write [good test docstrings](https://jml.io/test-docstrings/). @@ -272,7 +272,7 @@ If you want to reference multiple issues, copy the news fragment to another file Towncrier will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests. -`tox run -e changelog` will render the current changelog to the terminal if you have any doubts. +`uv run tox run -e changelog` will render the current changelog to the terminal if you have any doubts. ## Governance diff --git a/.github/workflows/build-docset.yml b/.github/workflows/build-docset.yml index ae09dc045..5492c321b 100644 --- a/.github/workflows/build-docset.yml +++ b/.github/workflows/build-docset.yml @@ -31,7 +31,9 @@ jobs: python-version: "3.x" - uses: hynek/setup-cached-uv@34e35d30f1ebc7421a5cc733bca38dcc62603960 # v2.6.0 - - run: uvx --with=tox-uv tox run -e docset + - run: > + uv run --only-group tox + tox run -e docset - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65c0a3fb5..86a8dfb45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - uses: hynek/setup-cached-uv@34e35d30f1ebc7421a5cc733bca38dcc62603960 # v2.6.0 - run: > - uvx --with=tox-uv + uv run --only-group tox tox run -e $PYTHON-mypy @@ -75,7 +75,7 @@ jobs: run: rm -rf src - run: > - uvx --with=tox-uv + uv run --only-group tox tox run --installpkg dist/*.whl -e $PYTHON-tests @@ -114,7 +114,7 @@ jobs: env: PYTHON: ${{ matrix.python-version }} run: > - uvx --with=tox-uv + uv run --only-group tox tox run --installpkg dist/*.whl -e $(echo $PYTHON | tr -d -)-tests @@ -172,7 +172,9 @@ jobs: - run: tar xf dist/*.tar.gz --strip-components=1 - uses: hynek/setup-cached-uv@34e35d30f1ebc7421a5cc733bca38dcc62603960 # v2.6.0 - - run: uvx --with=tox-uv tox run -e docs-doctests,changelog + - run: > + uv run --only-group tox + tox run -e docs-doctests,changelog lint: name: Run linters @@ -184,8 +186,7 @@ jobs: - uses: hynek/setup-cached-uv@34e35d30f1ebc7421a5cc733bca38dcc62603960 # v2.6.0 - run: > - uvx --with=tox-uv - --python $(cat .python-version-default) + uv run --only-group tox tox run -f lint typing: @@ -195,11 +196,13 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version-file: .python-version-default - uses: hynek/setup-cached-uv@34e35d30f1ebc7421a5cc733bca38dcc62603960 # v2.6.0 - run: > - uvx --with=tox-uv - --python $(cat .python-version-default) + uv run --only-group tox tox run -f typing install-dev: diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index c96acc1b0..1476752cf 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -45,7 +45,9 @@ jobs: uses: CodSpeedHQ/action@88472375d0a4572cf70a9f1fe3a4e0ab8da1b924 # v5.0.1 with: mode: simulation - run: uvx --with tox-uv tox run -e codspeed + run: > + uv run --only-group tox + tox run -e codspeed imports: name: Measure import times diff --git a/pyproject.toml b/pyproject.toml index dcb6f68d0..eef0a7dc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ benchmark = [ "pytest-codspeed", "pytest-xdist[psutil]", ] +tox = ["tox>4", "tox-uv-bare"] docs = [ "cogapp", "furo", @@ -73,6 +74,7 @@ docs = [ ] docs-watch = [{ include-group = "docs" }, "watchfiles"] dev = [ + { include-group = "tox" }, { include-group = "tests" }, { include-group = "lint" }, { include-group = "pyrefly" }, diff --git a/tox.ini b/tox.ini index b4946b368..04c9b9bef 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ [tox] requires = tox>=4.51.0 - tox-uv + tox-uv-bare env_list = lint-{ruff,pre-commit}, 3.1{0-5}-tests, @@ -98,14 +98,16 @@ commands = docs [testenv:docs-sponsors] +description = Ensure sponsor logos are up to date. runner = uv-venv-runner +base_python_file = .python-version-default package = skip -description = Ensure sponsor logos are up to date. deps = cogapp commands = cog -rP README.md docs/index.md [testenv:lint-{ruff,pre-commit}] +base_python_file = .python-version-default dependency_groups = lint package = skip commands = @@ -124,16 +126,19 @@ commands = [testenv:typing-pyright] dependency_groups = pyright +base_python_file = .python-version-default commands = pyright pytest tests/test_pyright.py -vv [testenv:typing-ty] dependency_groups = ty +base_python_file = .python-version-default commands = ty check typing_tests/baseline.py [testenv:typing-pyrefly] dependency_groups = pyrefly +base_python_file = .python-version-default commands = pyrefly check diff --git a/uv.lock b/uv.lock index 4592cdac9..986595b7e 100644 --- a/uv.lock +++ b/uv.lock @@ -121,6 +121,8 @@ dev = [ { name = "pytest" }, { name = "pytest-xdist", extra = ["psutil"] }, { name = "ruff" }, + { name = "tox" }, + { name = "tox-uv-bare" }, { name = "ty" }, ] docs = [ @@ -183,6 +185,10 @@ tests = [ { name = "pytest" }, { name = "pytest-xdist", extra = ["psutil"] }, ] +tox = [ + { name = "tox" }, + { name = "tox-uv-bare" }, +] ty = [ { name = "cloudpickle", marker = "platform_python_implementation == 'CPython'" }, { name = "hypothesis" }, @@ -220,6 +226,8 @@ dev = [ { name = "pytest", specifier = ">9" }, { name = "pytest-xdist", extras = ["psutil"] }, { name = "ruff", specifier = ">=0.16" }, + { name = "tox", specifier = ">4" }, + { name = "tox-uv-bare" }, { name = "ty" }, ] docs = [ @@ -276,6 +284,10 @@ tests = [ { name = "pytest", specifier = ">9" }, { name = "pytest-xdist", extras = ["psutil"] }, ] +tox = [ + { name = "tox", specifier = ">4" }, + { name = "tox-uv-bare" }, +] ty = [ { name = "cloudpickle", marker = "platform_python_implementation == 'CPython'" }, { name = "hypothesis" }, @@ -307,6 +319,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, ] +[[package]] +name = "cachetools" +version = "7.1.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/af/861ebc2e318a5c3300e3eb63bc4d30f3d70a46d13b360093728ac0705eed/cachetools-7.1.6.tar.gz", hash = "sha256:c7a79e7f30ba9943c1cefd08cc36f006aaae086e017af9166f1d59d6170c47e1", size = 40572, upload-time = "2026-07-23T22:47:53.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/f2/2086ba18a925a73586c4d4e61d25f4a6058e56fd00d77ce8f1d361ab4c9b/cachetools-7.1.6-py3-none-any.whl", hash = "sha256:2c12e255780330af28b91bb7fb96cce4c766f04e38396b9a24510190a5827096", size = 16954, upload-time = "2026-07-23T22:47:52.397Z" }, +] + [[package]] name = "certifi" version = "2026.7.22" @@ -554,6 +575,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, ] +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -601,6 +631,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] +[[package]] +name = "filelock" +version = "3.32.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, +] + [[package]] name = "furo" version = "2025.12.19" @@ -1124,6 +1163,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, ] +[[package]] +name = "platformdirs" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -1206,6 +1254,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/4f/a6a2e2b202d7fd97eadfe90979845b8706676b41cbd3b42ba75adf329d1f/Pympler-1.1-py3-none-any.whl", hash = "sha256:5b223d6027d0619584116a0cbc28e8d2e378f7a79c1e5e024f9ff3b673c58506", size = 165766, upload-time = "2024-06-28T19:56:05.087Z" }, ] +[[package]] +name = "pyproject-api" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/bd/2f985c12bf33fdd8637e3a8f9418d6806f177601dee7c4924d0b2bb28650/pyproject_api-1.11.0.tar.gz", hash = "sha256:b8807d85a293e6c9f133e6575946fed45f1d42b22d58c780b33aa2421a799549", size = 23787, upload-time = "2026-07-21T13:09:33.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/6a/2822ee12bafe87af8f6cc620f7d1f126e77f82ce56ba888b94a9af5a979c/pyproject_api-1.11.0-py3-none-any.whl", hash = "sha256:860060c8832dce983b5eec6f41c4c43eb3ec06ff7332387a63acdf5ca27b68d8", size = 13275, upload-time = "2026-07-21T13:09:32.559Z" }, +] + [[package]] name = "pyrefly" version = "1.2.0" @@ -1331,6 +1392,19 @@ psutil = [ { name = "psutil" }, ] +[[package]] +name = "python-discovery" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, +] + [[package]] name = "pywin32" version = "312" @@ -2144,6 +2218,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + [[package]] name = "tomlkit" version = "0.15.1" @@ -2167,6 +2250,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl", hash = "sha256:b953d133d98f9aeae9084b56a3563fd2519dfc6ec33f61c9cd2c61ff243fb513", size = 65101, upload-time = "2025-08-30T11:41:53.644Z" }, ] +[[package]] +name = "tox" +version = "4.58.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "python-discovery" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomli-w" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/8e/4d2b1b2a81f4de1cd4e54fa40df1ab5f9bb88fe2e37461fc44aba8f9d302/tox-4.58.0.tar.gz", hash = "sha256:ab0b126a04dd56bc18e6d216386db09335247f2289b54cf534deb5c4ae3a8d2e", size = 296926, upload-time = "2026-07-21T13:10:36.622Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/3d/7ba55871e9d794d40b6c8424f2e5d1b267ea5d9a4bd2175e08b57960ba13/tox-4.58.0-py3-none-any.whl", hash = "sha256:dcae21f5f015f3a67658e35644cce0d1aa0dedcd06f3927f95d84e1717f6cea5", size = 223298, upload-time = "2026-07-21T13:10:34.731Z" }, +] + +[[package]] +name = "tox-uv-bare" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tox" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/df/9f90a59de8c87cece6e691eee53dc1cb0a824d73aea8f380ae2f6ecb71de/tox_uv_bare-1.36.0.tar.gz", hash = "sha256:d9b0a2fd0f74fa65d9597108f8a0ef7abb08651c9196a998a6073b781b45cfd0", size = 32548, upload-time = "2026-07-21T13:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/0a/6dc462e4fb543305283a6157c80f43e3d12ca4702da6ae6521d541c6b55c/tox_uv_bare-1.36.0-py3-none-any.whl", hash = "sha256:ba397dd0396df95a75744d4e42a50ee27207c0ffcf277b62ffba9c3de455a939", size = 22489, upload-time = "2026-07-21T13:09:55.389Z" }, +] + [[package]] name = "ty" version = "0.0.63" @@ -2210,6 +2330,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] +[[package]] +name = "virtualenv" +version = "21.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, +] + [[package]] name = "watchfiles" version = "1.2.0"