Skip to content
Merged
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
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
V=${{ matrix.python-version }}
DO_MYPY=1

if [[ "$V" == "3.7" || "$V" == "3.8" ]]; then
if [[ "$V" == "3.8" ]]; then
DO_MYPY=0
fi

Expand All @@ -73,7 +73,6 @@ jobs:
uv pip install --system tox

- run: uv pip install --system tox-uv
if: matrix.python-version != '3.7'

- run: python -Im tox run -e ${{ env.TOX_PYTHON }}-mypy
if: env.DO_MYPY == '1'
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: black

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.2
rev: v0.6.3
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
1 change: 1 addition & 0 deletions changelog.d/1340.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Python 3.7 has been dropped.
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ build-backend = "hatchling.build"
name = "attrs"
authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }]
license = { text = "MIT" }
requires-python = ">=3.7"
requires-python = ">=3.8"
description = "Classes Without Boilerplate"
keywords = ["class", "attribute", "boilerplate"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand All @@ -26,7 +25,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: PyPy",
"Typing :: Typed",
]
dependencies = ["importlib_metadata;python_version<'3.8'"]
dependencies = []
dynamic = ["version", "readme"]

[project.optional-dependencies]
Expand Down
8 changes: 2 additions & 6 deletions src/attr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
"""

from functools import partial
from typing import Callable
from typing import Callable, Protocol

from . import converters, exceptions, filters, setters, validators
from ._cmp import cmp_using
from ._compat import Protocol
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types
from ._make import (
Expand Down Expand Up @@ -85,10 +84,7 @@ def __getattr__(name: str) -> str:
msg = f"module {mod_name} has no attribute {name}"
raise AttributeError(msg)

try:
from importlib.metadata import metadata
except ImportError:
from importlib_metadata import metadata
from importlib.metadata import metadata

meta = metadata("attrs")

Expand Down
35 changes: 13 additions & 22 deletions src/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -76,29 +76,20 @@ NOTHING = _Nothing.NOTHING
# NOTE: Factory lies about its return type to make this possible:
# `x: List[int] # = Factory(list)`
# Work around mypy issue #4554 in the common case by using an overload.
if sys.version_info >= (3, 8):
from typing import Literal
@overload
def Factory(factory: Callable[[], _T]) -> _T: ...
@overload
def Factory(
factory: Callable[[Any], _T],
takes_self: Literal[True],
) -> _T: ...
@overload
def Factory(
factory: Callable[[], _T],
takes_self: Literal[False],
) -> _T: ...
from typing import Literal

else:
@overload
def Factory(factory: Callable[[], _T]) -> _T: ...
@overload
def Factory(
factory: Union[Callable[[Any], _T], Callable[[], _T]],
takes_self: bool = ...,
) -> _T: ...
@overload
def Factory(factory: Callable[[], _T]) -> _T: ...
@overload
def Factory(
factory: Callable[[Any], _T],
takes_self: Literal[True],
) -> _T: ...
@overload
def Factory(
factory: Callable[[], _T],
takes_self: Literal[False],
) -> _T: ...

In = TypeVar("In")
Out = TypeVar("Out")
Expand Down
9 changes: 0 additions & 9 deletions 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_8_PLUS = sys.version_info[:2] >= (3, 8)
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)
Expand All @@ -19,14 +18,6 @@
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)


if sys.version_info < (3, 8):
try:
from typing_extensions import Protocol
except ImportError: # pragma: no cover
Protocol = object
else:
from typing import Protocol # noqa: F401

if PY_3_14_PLUS: # pragma: no cover
import annotationlib

Expand Down
18 changes: 6 additions & 12 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
# having the thread-local in the globals here.
from . import _compat, _config, setters
from ._compat import (
PY_3_8_PLUS,
PY_3_10_PLUS,
PY_3_11_PLUS,
_AnnotationExtractor,
Expand Down Expand Up @@ -790,16 +789,11 @@ def _create_slots_class(self):
):
names += ("__weakref__",)

if PY_3_8_PLUS:
cached_properties = {
name: cached_property.func
for name, cached_property in cd.items()
if isinstance(cached_property, functools.cached_property)
}
else:
# `functools.cached_property` was introduced in 3.8.
# So can't be used before this.
cached_properties = {}
cached_properties = {
name: cached_property.func
for name, cached_property in cd.items()
if isinstance(cached_property, functools.cached_property)
}

# Collect methods with a `__class__` reference that are shadowed in the new class.
# To know to update them.
Expand Down Expand Up @@ -2213,7 +2207,7 @@ def _attrs_to_init_script(
# If pre init method has arguments, pass same arguments as `__init__`.
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"

# Python 3.7 doesn't allow backslashes in f strings.
# Python <3.12 doesn't allow backslashes in f-strings.
NL = "\n "
return (
f"""def {method_name}(self, {args}):
Expand Down
6 changes: 1 addition & 5 deletions tests/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

import attr

from attr._compat import PY_3_8_PLUS

from .utils import make_class


Expand Down Expand Up @@ -189,9 +187,7 @@ def init(self, *args, **kwargs):
cls_dict["__init__"] = init

bases = (object,)
if cached_property or (
PY_3_8_PLUS and cached_property is None and cached_property_flag
):
if cached_property or (cached_property is None and cached_property_flag):

class BaseWithCachedProperty:
@functools.cached_property
Expand Down
4 changes: 3 additions & 1 deletion tests/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import types

from typing import Protocol

import pytest

import attr
Expand Down Expand Up @@ -59,5 +61,5 @@ def test_attrsinstance_subclass_protocol():
It's possible to subclass AttrsInstance and Protocol at once.
"""

class Foo(attr.AttrsInstance, attr._compat.Protocol):
class Foo(attr.AttrsInstance, Protocol):
def attribute(self) -> int: ...
3 changes: 1 addition & 2 deletions tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import attr

from attr import _config
from attr._compat import PY_3_8_PLUS, PY_3_10_PLUS, PY_3_14_PLUS
from attr._compat import PY_3_10_PLUS, PY_3_14_PLUS
from attr._make import (
Attribute,
Factory,
Expand Down Expand Up @@ -1837,7 +1837,6 @@ class C2(C):

assert [C2] == C.__subclasses__()

@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
@pytest.mark.xfail(PY_3_14_PLUS, reason="Currently broken on nightly.")
def test_no_references_to_original_when_using_cached_property(self):
"""
Expand Down
9 changes: 2 additions & 7 deletions tests/test_packaging.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# SPDX-License-Identifier: MIT

import sys

from importlib import metadata

import pytest

import attr
import attrs


if sys.version_info < (3, 8):
import importlib_metadata as metadata
else:
from importlib import metadata


@pytest.fixture(name="mod", params=(attr, attrs))
def _mod(request):
return request.param
Expand Down
Loading