diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 488a1a91..00000000 --- a/.flake8 +++ /dev/null @@ -1,16 +0,0 @@ -[flake8] -max-line-length = 90 -ignore = - # irrelevant plugins - B3, - DW12, - # code is sometimes better without this - E129, - # Contradicts PEP8 nowadays - W503, - # consistency with mypy - W504 -per-file-ignores = - # stylistic rules we don't care about in tests - src/test_typing_extensions.py:E302,E306,E501,E701,E704, -noqa_require_code = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c686b6e1..9f062801 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ permissions: contents: read env: + FORCE_COLOR: 1 PIP_DISABLE_PIP_VERSION_CHECK: 1 concurrency: @@ -99,16 +100,10 @@ jobs: python-version: "3" cache: "pip" cache-dependency-path: "test-requirements.txt" - - name: Install dependencies - run: | - pip install -r test-requirements.txt - # not included in test-requirements.txt as it depends on typing-extensions, - # so it's a pain to have it installed locally - pip install flake8-noqa - + run: pip install -r test-requirements.txt - name: Lint implementation - run: flake8 --color always + run: ruff check create-issue-on-failure: name: Create an issue if daily tests failed diff --git a/doc/conf.py b/doc/conf.py index 40d3c6b7..42273604 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -5,8 +5,9 @@ import os.path import sys -from sphinx.writers.html5 import HTML5Translator + from docutils.nodes import Element +from sphinx.writers.html5 import HTML5Translator sys.path.insert(0, os.path.abspath('.')) diff --git a/pyproject.toml b/pyproject.toml index c9762f5f..5ee10946 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,3 +60,42 @@ email = "levkivskyi@gmail.com" [tool.flit.sdist] include = ["CHANGELOG.md", "README.md", "tox.ini", "*/*test*.py"] exclude = [] + +[tool.ruff] +line-length = 90 +target-version = "py38" + +[tool.ruff.lint] +select = [ + "B", + "C4", + "E", + "F", + "I", + "ISC001", + "PGH004", + "RUF", + "SIM201", + "SIM202", + "UP", + "W", +] + +# Ignore various "modernization" rules that tell you off for importing/using +# deprecated things from the typing module, etc. +ignore = ["UP006", "UP007", "UP013", "UP014", "UP019", "UP035", "UP038"] + +[tool.ruff.lint.per-file-ignores] +"!src/typing_extensions.py" = [ + "B018", + "B024", + "C4", + "E302", + "E306", + "E501", + "E701", +] + +[tool.ruff.lint.isort] +extra-standard-library = ["tomllib"] +known-first-party = ["typing_extensions", "_typed_dict_test_helper"] diff --git a/src/_typed_dict_test_helper.py b/src/_typed_dict_test_helper.py index c5582b15..73cf9199 100644 --- a/src/_typed_dict_test_helper.py +++ b/src/_typed_dict_test_helper.py @@ -1,7 +1,8 @@ from __future__ import annotations from typing import Generic, Optional, T -from typing_extensions import TypedDict, Annotated, Required + +from typing_extensions import Annotated, Required, TypedDict # this class must not be imported into test_typing_extensions.py at top level, otherwise diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index b8cf2b2b..7214b709 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -1,39 +1,97 @@ -import sys import abc -import gc -import io -import contextlib import collections -from collections import defaultdict import collections.abc +import contextlib import copy -from functools import lru_cache +import gc import importlib import inspect +import io import pickle import re import subprocess +import sys import tempfile import textwrap import types -from pathlib import Path -from unittest import TestCase, main, skipUnless, skipIf -from unittest.mock import patch import typing import warnings +from collections import defaultdict +from functools import lru_cache +from pathlib import Path +from unittest import TestCase, main, skipIf, skipUnless +from unittest.mock import patch import typing_extensions -from typing_extensions import NoReturn, Any, ClassVar, Final, IntVar, Literal, Type, NewType, TypedDict, Self -from typing_extensions import TypeAlias, ParamSpec, Concatenate, ParamSpecArgs, ParamSpecKwargs, TypeGuard -from typing_extensions import Awaitable, AsyncIterator, AsyncContextManager, Required, NotRequired, ReadOnly -from typing_extensions import Protocol, runtime, runtime_checkable, Annotated, final, is_typeddict -from typing_extensions import TypeVarTuple, Unpack, dataclass_transform, reveal_type, Never, assert_never, LiteralString -from typing_extensions import assert_type, get_type_hints, get_origin, get_args, get_original_bases -from typing_extensions import clear_overloads, get_overloads, overload, Iterator -from typing_extensions import NamedTuple, TypeIs, no_type_check, Dict -from typing_extensions import override, deprecated, Buffer, TypeAliasType, TypeVar, get_protocol_members, is_protocol -from typing_extensions import Doc, NoDefault, List, Union, AnyStr, Iterable, Generic, Optional, Set, Tuple, Callable from _typed_dict_test_helper import Foo, FooGeneric, VeryAnnotated +from typing_extensions import ( + Annotated, + Any, + AnyStr, + AsyncContextManager, + AsyncIterator, + Awaitable, + Buffer, + Callable, + ClassVar, + Concatenate, + Dict, + Doc, + Final, + Generic, + IntVar, + Iterable, + Iterator, + List, + Literal, + LiteralString, + NamedTuple, + Never, + NewType, + NoDefault, + NoReturn, + NotRequired, + Optional, + ParamSpec, + ParamSpecArgs, + ParamSpecKwargs, + Protocol, + ReadOnly, + Required, + Self, + Set, + Tuple, + Type, + TypeAlias, + TypeAliasType, + TypedDict, + TypeGuard, + TypeIs, + TypeVar, + TypeVarTuple, + Union, + Unpack, + assert_never, + assert_type, + clear_overloads, + dataclass_transform, + deprecated, + final, + get_args, + get_origin, + get_original_bases, + get_overloads, + get_protocol_members, + get_type_hints, + is_protocol, + is_typeddict, + no_type_check, + overload, + override, + reveal_type, + runtime, + runtime_checkable, +) NoneType = type(None) T = TypeVar("T") @@ -179,14 +237,14 @@ def g_bad_ann(): class BaseTestCase(TestCase): def assertIsSubclass(self, cls, class_or_tuple, msg=None): if not issubclass(cls, class_or_tuple): - message = f'{cls!r} is not a subclass of {repr(class_or_tuple)}' + message = f'{cls!r} is not a subclass of {class_or_tuple!r}' if msg is not None: message += f' : {msg}' raise self.failureException(message) def assertNotIsSubclass(self, cls, class_or_tuple, msg=None): if issubclass(cls, class_or_tuple): - message = f'{cls!r} is a subclass of {repr(class_or_tuple)}' + message = f'{cls!r} is a subclass of {class_or_tuple!r}' if msg is not None: message += f' : {msg}' raise self.failureException(message) @@ -765,11 +823,11 @@ def test_repr(self): mod_name = 'typing' else: mod_name = 'typing_extensions' - self.assertEqual(repr(Required), mod_name + '.Required') + self.assertEqual(repr(Required), f'{mod_name}.Required') cv = Required[int] - self.assertEqual(repr(cv), mod_name + '.Required[int]') + self.assertEqual(repr(cv), f'{mod_name}.Required[int]') cv = Required[Employee] - self.assertEqual(repr(cv), mod_name + '.Required[%s.Employee]' % __name__) + self.assertEqual(repr(cv), f'{mod_name}.Required[{__name__}.Employee]') def test_cannot_subclass(self): with self.assertRaises(TypeError): @@ -810,11 +868,11 @@ def test_repr(self): mod_name = 'typing' else: mod_name = 'typing_extensions' - self.assertEqual(repr(NotRequired), mod_name + '.NotRequired') + self.assertEqual(repr(NotRequired), f'{mod_name}.NotRequired') cv = NotRequired[int] - self.assertEqual(repr(cv), mod_name + '.NotRequired[int]') + self.assertEqual(repr(cv), f'{mod_name}.NotRequired[int]') cv = NotRequired[Employee] - self.assertEqual(repr(cv), mod_name + '.NotRequired[%s.Employee]' % __name__) + self.assertEqual(repr(cv), f'{mod_name}.NotRequired[{ __name__}.Employee]') def test_cannot_subclass(self): with self.assertRaises(TypeError): @@ -872,7 +930,7 @@ def test_illegal_parameters_do_not_raise_runtime_errors(self): Literal[int] Literal[Literal[1, 2], Literal[4, 5]] Literal[3j + 2, ..., ()] - Literal[b"foo", u"bar"] + Literal[b"foo", "bar"] Literal[{"foo": 3, "bar": 4}] Literal[T] @@ -1747,7 +1805,7 @@ class D: ... self.assertIsSubclass(D, A) self.assertIsSubclass(D, B) - class M(): ... + class M: ... collections.abc.Generator.register(M) self.assertIsSubclass(M, typing_extensions.Generator) @@ -2988,7 +3046,7 @@ class NonP(P): class NonPR(PR): pass class C(metaclass=abc.ABCMeta): x = 1 - class D(metaclass=abc.ABCMeta): # noqa: B024 + class D(metaclass=abc.ABCMeta): def meth(self): pass # noqa: B027 self.assertNotIsInstance(C(), NonP) self.assertNotIsInstance(D(), NonPR) @@ -3274,7 +3332,7 @@ def test_none_treated_correctly(self): @runtime_checkable class P(Protocol): x: int = None - class B(object): pass + class B: pass self.assertNotIsInstance(B(), P) class C: x = 1 @@ -5243,7 +5301,7 @@ def test_repr(self): mod_name = 'typing' else: mod_name = 'typing_extensions' - self.assertEqual(repr(LiteralString), '{}.LiteralString'.format(mod_name)) + self.assertEqual(repr(LiteralString), f'{mod_name}.LiteralString') def test_cannot_subscript(self): with self.assertRaises(TypeError): @@ -5297,7 +5355,7 @@ def test_repr(self): mod_name = 'typing' else: mod_name = 'typing_extensions' - self.assertEqual(repr(Self), '{}.Self'.format(mod_name)) + self.assertEqual(repr(Self), f'{mod_name}.Self') def test_cannot_subscript(self): with self.assertRaises(TypeError): @@ -5556,7 +5614,7 @@ def stmethod(): ... def prop(self): ... @final - @lru_cache() # noqa: B019 + @lru_cache # noqa: B019 def cached(self): ... # Use getattr_static because the descriptor returns the @@ -6312,8 +6370,8 @@ def test_or(self): X = TypeVar('X') # use a string because str doesn't implement # __or__/__ror__ itself - self.assertEqual(X | "x", Union[X, "x"]) # noqa: F821 - self.assertEqual("x" | X, Union["x", X]) # noqa: F821 + self.assertEqual(X | "x", Union[X, "x"]) + self.assertEqual("x" | X, Union["x", X]) # make sure the order is correct self.assertEqual(get_args(X | "x"), (X, typing.ForwardRef("x"))) self.assertEqual(get_args("x" | X), (typing.ForwardRef("x"), X)) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 2afb49a7..abf6f41e 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -418,7 +418,7 @@ def clear_overloads(): if sys.version_info >= (3, 13, 0, "beta"): - from typing import ContextManager, AsyncContextManager, Generator, AsyncGenerator + from typing import AsyncContextManager, AsyncGenerator, ContextManager, Generator else: def _is_dunder(attr): return attr.startswith('__') and attr.endswith('__') @@ -739,8 +739,8 @@ def close(self): ... not their type signatures! """ if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False): - raise TypeError('@runtime_checkable can be only applied to protocol classes,' - ' got %r' % cls) + raise TypeError(f'@runtime_checkable can be only applied to protocol classes,' + f' got {cls!r}') cls._is_runtime_protocol = True # typing.Protocol classes on <=3.11 break if we execute this block, @@ -1271,7 +1271,7 @@ def __repr__(self): def __reduce__(self): return operator.getitem, ( - Annotated, (self.__origin__,) + self.__metadata__ + Annotated, (self.__origin__, *self.__metadata__) ) def __eq__(self, other): @@ -1397,7 +1397,7 @@ def get_args(tp): get_args(Callable[[], T][int]) == ([], int) """ if isinstance(tp, _AnnotatedAlias): - return (tp.__origin__,) + tp.__metadata__ + return (tp.__origin__, *tp.__metadata__) if isinstance(tp, (typing._GenericAlias, _typing_GenericAlias)): if getattr(tp, "_special", False): return () @@ -1811,7 +1811,7 @@ def _concatenate_getitem(self, parameters): # 3.10+ if hasattr(typing, 'Concatenate'): Concatenate = typing.Concatenate - _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa: F811 + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # 3.9 elif sys.version_info[:2] >= (3, 9): @_ExtensionsSpecialForm @@ -3248,7 +3248,7 @@ class Employee(NamedTuple): if hasattr(collections.abc, "Buffer"): Buffer = collections.abc.Buffer else: - class Buffer(abc.ABC): + class Buffer(abc.ABC): # noqa: B024 """Base class for classes that implement the buffer protocol. The buffer protocol allows Python objects to expose a low-level diff --git a/test-requirements.txt b/test-requirements.txt index 675b2c5d..7242d3b5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1 @@ -flake8 -flake8-bugbear +ruff==0.4.5