Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a fully locked test environment #1349

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ci:

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
rev: v0.9.2
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
47 changes: 27 additions & 20 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ name = "attrs"
authors = [{ name = "Hynek Schlawack", email = "[email protected]" }]
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.8"
requires-python = ">=3.9"
description = "Classes Without Boilerplate"
keywords = ["class", "attribute", "boilerplate"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand All @@ -28,29 +27,41 @@ classifiers = [
dependencies = []
dynamic = ["version", "readme"]

[project.optional-dependencies]
[project.urls]
Documentation = "https://www.attrs.org/"
Changelog = "https://www.attrs.org/en/stable/changelog.html"
GitHub = "https://github.com/python-attrs/attrs"
Funding = "https://github.com/sponsors/hynek"
Tidelift = "https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi"


[dependency-groups]
tests-mypy = [
'pytest-mypy-plugins; platform_python_implementation == "CPython" and python_version >= "3.10"',
# Since the mypy error messages keep changing, we have to keep updating this
# pin.
'mypy>=1.11.1; platform_python_implementation == "CPython" and python_version >= "3.10"',
]
tests = [
{ include-group = "tests-mypy" },
# For regression test to ensure cloudpickle compat doesn't break.
'cloudpickle; platform_python_implementation == "CPython"',
"hypothesis",
"pympler",
# 4.3.0 dropped last use of `convert`
"pytest>=4.3.0",
"pytest-xdist[psutil]",
"attrs[tests-mypy]",
]
cov = [
"attrs[tests]",
{ include-group = "tests" },
# Ensure coverage is new enough for `source_pkgs`.
"coverage[toml]>=5.3",
]
benchmark = ["pytest-codspeed", "pytest-xdist[psutil]", "attrs[tests]"]
pyright = ["pyright", { include-group = "tests" }]
benchmark = [
{ include-group = "tests" },
"pytest-codspeed",
"pytest-xdist[psutil]",
]
docs = [
"cogapp",
"furo",
Expand All @@ -59,17 +70,10 @@ docs = [
"sphinx-notfound-page",
"sphinxcontrib-towncrier",
# See https://github.com/sphinx-contrib/sphinxcontrib-towncrier/issues/92
# Pin also present in tox.ini
"towncrier<24.7",
]
dev = ["attrs[tests]", "pre-commit-uv"]

[project.urls]
Documentation = "https://www.attrs.org/"
Changelog = "https://www.attrs.org/en/stable/changelog.html"
GitHub = "https://github.com/python-attrs/attrs"
Funding = "https://github.com/sponsors/hynek"
Tidelift = "https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi"
docs-watch = [{ include-group = "docs" }, "watchfiles"]
dev = [{ include-group = "tests" }]


[tool.hatch.version]
Expand Down Expand Up @@ -227,6 +231,8 @@ ignore = [
"TD", # we don't follow other people's todo style
"TRY301", # I'm sorry, but this makes not sense for us.
"UP031", # format() is slow as molasses; % and f'' FTW.
"UP006", # replace Dict etc by dict etc later.
"UP035", # replace Dict etc by dict etc later.
]

[tool.ruff.lint.per-file-ignores]
Expand All @@ -243,6 +249,7 @@ ignore = [
"PLR0124", # pointless comparison in tests aren't pointless
"PT011", # broad is fine
"PT012", # sometimes we need more than a single stmt
"RUF009", # using results of function calls as defaults is fine
"RUF012", # we don't do ClassVar annotations in tests
"S", # security concerns don't matter in tests
"SIM201", # sometimes we need to check `not ==`
Expand All @@ -254,10 +261,10 @@ ignore = [
"src/*/*.pyi" = ["ALL"] # TODO
"tests/test_annotations.py" = ["FA100"]
"tests/typing_example.py" = [
"E741", # ambiguous variable names don't matter in type checks
"B018", # useless expressions aren't useless in type checks
"B015", # pointless comparison in type checks aren't pointless
"UP037", # we test some older syntaxes on purpose
"E741", # ambiguous variable names don't matter in type checks
"B018", # useless expressions aren't useless in type checks
"B015", # pointless comparison in type checks aren't pointless
"UP037", # we test some older syntaxes on purpose
]

[tool.ruff.lint.isort]
Expand Down
1 change: 0 additions & 1 deletion src/attr/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@


PYPY = platform.python_implementation() == "PyPy"
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
Expand Down
11 changes: 6 additions & 5 deletions src/attr/_funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import copy

from ._compat import PY_3_9_PLUS, get_generic_base
from ._compat import get_generic_base
from ._make import _OBJ_SETATTR, NOTHING, fields
from .exceptions import AttrsAttributeNotFoundError

Expand Down Expand Up @@ -450,10 +450,11 @@ class yet.
if getattr(cls, "__attrs_types_resolved__", None) != cls:
import typing

kwargs = {"globalns": globalns, "localns": localns}

if PY_3_9_PLUS:
kwargs["include_extras"] = include_extras
kwargs = {
"globalns": globalns,
"localns": localns,
"include_extras": include_extras,
}

hints = typing.get_type_hints(cls, **kwargs)
for field in fields(cls) if attribs is None else attribs:
Expand Down
2 changes: 1 addition & 1 deletion src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -2285,7 +2285,7 @@ def _attrs_to_init_script(
NL = "\n "
return (
f"""def {method_name}(self, {args}):
{NL.join(lines) if lines else 'pass'}
{NL.join(lines) if lines else "pass"}
""",
names_for_globals,
annotations,
Expand Down
5 changes: 3 additions & 2 deletions tests/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pytest

import attr
import attrs

from attr._compat import PY_3_14_PLUS
from attr._make import _is_class_var
Expand Down Expand Up @@ -107,7 +108,7 @@ def test_auto_attribs(self, slots):
class C:
cls_var: typing.ClassVar[int] = 23
a: int
x: typing.List[int] = attr.Factory(list)
x: typing.List[int] = attrs.Factory(list)
y: int = 2
z: int = attr.ib(default=3)
foo: typing.Any = None
Expand Down Expand Up @@ -407,7 +408,7 @@ class C:
cls_var2: "ClassVar[int]" = 23
cls_var3: "t.ClassVar[int]" = 23
a: "int"
x: "typing.List[int]" = attr.Factory(list)
x: "typing.List[int]" = attrs.Factory(list)
y: "int" = 2
z: "int" = attr.ib(default=3)
foo: "typing.Any" = None
Expand Down
3 changes: 1 addition & 2 deletions tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,8 +1074,7 @@ def test_repr(self):
v = not_(wrapped)

assert (
f"<not_ validator wrapping {wrapped!r}, "
f"capturing {v.exc_types!r}>"
f"<not_ validator wrapping {wrapped!r}, capturing {v.exc_types!r}>"
) == repr(v)

def test_success_because_fails(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/typing_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ class Validated2:

@attrs.define
class Validated3:
num: int = attr.field(validator=attr.validators.ge(0))
num: int = attrs.field(validator=attr.validators.ge(0))


with attr.validators.disabled():
Expand Down
46 changes: 25 additions & 21 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
min_version = 4
env_list =
pre-commit,
py3{8,9,10,11,12,13}-tests,
py3{9,10,11,12,13}-tests,
py3{10,11,12,13}-mypy,
pypy3-tests,
pyright,
Expand All @@ -16,45 +16,46 @@ pass_env = SETUPTOOLS_SCM_PRETEND_VERSION


[testenv]
runner = uv-venv-lock-runner
package = wheel
wheel_build_env = .pkg
extras =
dependency_groups =
tests: tests
mypy: tests-mypy
commands =
tests: pytest {posargs:-n auto}
tests: pytest {posargs}
mypy: mypy tests/typing_example.py
mypy: mypy src/attrs/__init__.pyi src/attr/__init__.pyi src/attr/_typing_compat.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi

[testenv:pypy3-tests]
extras = tests
dependency_groups = tests
commands = pytest tests/test_functional.py

[testenv:py3{8,10,13}-tests]
extras = cov
[testenv:py3{9,10,13}-tests]
dependency_groups = cov
# Python 3.6+ has a number of compile-time warnings on invalid string escapes.
# PYTHONWARNINGS=d makes them visible during the tox run.
set_env =
COVERAGE_PROCESS_START={toxinidir}/pyproject.toml
PYTHONWARNINGS=d
commands_pre = python -c 'import pathlib; pathlib.Path("{env_site_packages_dir}/cov.pth").write_text("import coverage; coverage.process_startup()")'
# We group xdist execution by file because otherwise the Mypy tests have race conditions.
commands = coverage run -m pytest {posargs:-n auto --dist loadfile}
commands =
coverage run -m pytest {posargs}

[testenv:coverage-report]
# Keep base_python in-sync with .python-version-default
base_python = py313
# Keep depends in-sync with testenv above that has cov extra.
depends = py3{8,10,13}-tests
# Keep depends in-sync with testenv above that has the cov dependency group.
depends = py3{9,10,13}-tests
skip_install = true
deps = coverage[toml]>=5.3
dependency_groups = cov
commands =
coverage combine
coverage report


[testenv:codspeed]
extras = benchmark
dependency_groups = benchmark
pass_env =
CODSPEED_TOKEN
CODSPEED_ENV
Expand All @@ -65,9 +66,10 @@ commands = pytest --codspeed -n auto bench/test_benchmarks.py


[testenv:docs-{build,doctests,linkcheck}]
# Keep base_python in sync with ci.yml/docs and .readthedocs.yaml.
runner = uv-venv-lock-runner
# Keep base_python in-sync with ci.yml/docs and .readthedocs.yaml.
base_python = py313
extras = docs
dependency_groups = docs
commands =
build: sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html
doctests: sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html
Expand All @@ -76,46 +78,48 @@ commands =
[testenv:docs-watch]
package = editable
base_python = {[testenv:docs-build]base_python}
extras = {[testenv:docs-build]extras}
dependency_groups = docs-watch
deps = watchfiles
commands =
watchfiles \
--ignore-paths docs/_build/ \
'sphinx-build -W -n --jobs auto -b html -d {envtmpdir}/doctrees docs docs/_build/html' \
README.md \
src \
docs

[testenv:docs-sponsors]
runner = uv-venv-runner
skip_install = true
description = Ensure sponsor logos are up to date.
deps = cogapp
commands = cog -rP README.md docs/index.md


[testenv:pre-commit]
runner = uv-venv-runner
skip_install = true
deps = pre-commit-uv
commands = pre-commit run --all-files


[testenv:changelog]
# See https://github.com/sphinx-contrib/sphinxcontrib-towncrier/issues/92
# Pin also present in pyproject.toml
deps = towncrier<24.7
dependency_groups = docs
skip_install = true
commands =
towncrier --version
towncrier build --version main --draft


[testenv:pyright]
extras = tests
deps = pyright>=1.1.380
dependency_groups = pyright
commands = pytest tests/test_pyright.py -vv


[testenv:docset]
runner = uv-venv-runner
deps = doc2dash
extras = docs
dependency_groups = docs
allowlist_externals =
rm
cp
Expand Down
Loading
Loading