Thanks to visit codestin.com
Credit goes to github.com

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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ jobs:

- run: uvx --with=tox-uv tox run -e docs-doctests,changelog

pyright:
name: Check types using pyright
typing:
name: Check types using supported type checkers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand All @@ -191,7 +191,7 @@ jobs:
- run: >
uvx --with=tox-uv
--python $(cat .python-version-default)
tox run -e pyright
tox run -f typing

install-dev:
name: Verify dev env
Expand Down Expand Up @@ -222,7 +222,7 @@ jobs:
- tests-pypy
- docs
- install-dev
- pyright
- typing

runs-on: ubuntu-latest

Expand Down
13 changes: 8 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ tests = [
]
cov = [{ include-group = "tests" }, "coverage[toml]"]
pyright = ["pyright", { include-group = "tests" }]
ty = ["ty", { include-group = "tests" }]
pyrefly = ["pyrefly", { include-group = "tests" }]
benchmark = [
{ include-group = "tests" },
"pytest-codspeed",
Expand Down Expand Up @@ -254,11 +256,12 @@ 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
"typing-examples/*" = [
"INP001", # not for imports
"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
21 changes: 17 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# SPDX-License-Identifier: MIT

[tox]
min_version = 4
env_list =
pre-commit,
py3{9,10,11,12,13,14}-tests,
py3{10,11,12,13,14}-mypy,
pypy3-tests,
pyright,
# Mypy needs to run within the respective Python version
typing-{pyright,ty,pyrefly}
docs-{sponsors,doctests},
changelog,
coverage-combine,
Expand All @@ -26,7 +29,7 @@ dependency_groups =
commands =
tests: pytest {posargs:-n auto}
mypy: pytest -k test_mypy
mypy: mypy tests/typing_example.py
mypy: mypy typing-examples
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]
Expand Down Expand Up @@ -121,9 +124,19 @@ commands =
towncrier build --version main --draft


[testenv:pyright]
[testenv:typing-pyright]
dependency_groups = pyright
commands = pytest tests/test_pyright.py -vv
commands =
pyright typing-examples/baseline_examples.py
pytest tests/test_pyright.py -vv

[testenv:typing-ty]
dependency_groups = ty
commands = ty check typing-examples/baseline_examples.py

[testenv:typing-pyrefly]
dependency_groups = pyrefly
commands = pyrefly check typing-examples/baseline_examples.py


[testenv:docset]
Expand Down
7 changes: 7 additions & 0 deletions typing-examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Typing examples

The files in this directory are **not** meant to be executed.

They are type-checked using various type checkers to ensure our type stubs are correct.

See the environments starting with `typing-` in [`tox.ini`](../tox.ini).
177 changes: 177 additions & 0 deletions typing-examples/baseline_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# SPDX-License-Identifier: MIT

"""
Baseline features that should be supported by all type checkers.
"""

from __future__ import annotations

from typing import Any

import attrs


@attrs.define(order=True)
class NGClass:
x: int = attrs.field(default=42)


ngc = NGClass(1)


@attrs.mutable(slots=False)
class NGClass2:
x: int


ngc2 = NGClass2(1)


@attrs.frozen(str=True)
class NGFrozen:
x: int


ngf = NGFrozen(1)

attrs.fields(NGFrozen).x.evolve(eq=False)
a = attrs.fields(NGFrozen).x
a.evolve(repr=False)


@attrs.define
class C:
a: int


c = C(1)
c.a


@attrs.frozen
class D:
a: int


D(1).a


@attrs.define
class Derived(C):
b: int


Derived(1, 2).a
Derived(1, 2).b


@attrs.define
class Error(Exception):
x: int


try:
raise Error(1)
except Error as e:
e.x
e.args
str(e)


@attrs.define
class AliasExample:
without_alias: int
_with_alias: int = attrs.field(alias="_with_alias")


attrs.fields(AliasExample).without_alias.alias
attrs.fields(AliasExample)._with_alias.alias


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


attrs.validators.set_disabled(True)
attrs.validators.set_disabled(False)


with attrs.validators.disabled():
Validated(num=-1)


@attrs.define
class WithCustomRepr:
a: int = attrs.field(repr=True)
b: str = attrs.field(repr=False)
c: str = attrs.field(repr=lambda value: "c is for cookie")
d: bool = attrs.field(repr=str)


@attrs.define(on_setattr=attrs.setters.validate)
class ValidatedSetter2:
a: int
b: str = attrs.field(on_setattr=attrs.setters.NO_OP)
c: bool = attrs.field(on_setattr=attrs.setters.frozen)
d: int = attrs.field(
on_setattr=[attrs.setters.convert, attrs.setters.validate]
)
e: bool = attrs.field(
on_setattr=attrs.setters.pipe(
attrs.setters.convert, attrs.setters.validate
)
)


@attrs.define(eq=True, order=True)
class OrderFlags:
a: int = attrs.field(eq=False, order=False)
b: int = attrs.field(eq=True, order=True)


# field_transformer
def ft_hook2(
cls: type, attribs: list[attrs.Attribute]
) -> list[attrs.Attribute]:
return attribs


@attrs.define(field_transformer=ft_hook2)
class TransformedAttrs2:
x: int


@attrs.define
class FactoryTest:
a: list[int] = attrs.field(default=attrs.Factory(list))
b: list[Any] = attrs.field(default=attrs.Factory(list, False))
c: list[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True))


attrs.asdict(FactoryTest())
attrs.asdict(FactoryTest(), retain_collection_types=False)


@attrs.define(match_args=False)
class MatchArgs2:
a: int
b: int


# NG versions of asdict/astuple
attrs.asdict(MatchArgs2(1, 2))
attrs.astuple(MatchArgs2(1, 2))


def accessing_from_attrs() -> None:
"""
Use a function to keep the ns clean.
"""
attrs.converters.optional
attrs.exceptions.FrozenError
attrs.filters.include
attrs.filters.exclude
attrs.setters.frozen
attrs.validators.and_
attrs.cmp_using
Loading