diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml new file mode 100644 index 0000000..0a993aa --- /dev/null +++ b/.github/workflows/semantic-pr.yaml @@ -0,0 +1,20 @@ +name: "Semantic PR Check" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd8a123..0ada126 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,26 @@ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 hooks: - - id: end-of-file-fixer - - id: trailing-whitespace - - id: check-added-large-files - - id: check-toml - - id: check-yaml -- repo: https://github.com/psf/black - rev: 23.7.0 + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files + - id: check-toml + - id: check-yaml + - id: mixed-line-ending + - repo: https://github.com/psf/black + rev: 24.4.2 hooks: - - id: black -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.287 + - id: black + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.10 hooks: - - id: ruff - args: [--fix, --exit-non-zero-on-fix] + - id: ruff + args: [ --fix, --exit-non-zero-on-fix ] + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + additional_dependencies: + - tomli + args: [ --write-changes ] diff --git a/docs/changelog.rst b/docs/changelog.rst index eabfb93..0e89750 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -219,7 +219,7 @@ Bug fixes --------- - Rotation in 3D now returns the correct transformation if the axis is not a normalized vector -- Line.perpendicular now also works for points tha lie on the line +- Line.perpendicular now also works for points that lie on the line 0.1.1 - released (2.2.2019) --------------------------- diff --git a/docs/conf.py b/docs/conf.py index dffdd8d..7c137f7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ import geometer project = "geometer" -copyright = "2022, Jan Müller" +copyright = "2023, Jan Müller" author = "Jan Müller" # The short X.Y version @@ -246,3 +246,4 @@ def linkcode_resolve(domain, info): # -- Extension configuration ------------------------------------------------- autodoc_typehints = "description" autodoc_typehints_description_target = "documented" +autodoc_type_aliases = {"npt.ArrayLike": "npt.ArrayLike"} diff --git a/geometer/base.py b/geometer/base.py index 95599a4..f467215 100644 --- a/geometer/base.py +++ b/geometer/base.py @@ -69,14 +69,14 @@ def __init__( if len(args) == 1: if isinstance(args[0], Tensor): - self.array = np.array(args[0].array, **kwargs) + self.array = np.array(args[0].array, **kwargs) # type: ignore[call-overload] self._covariant_indices = args[0]._covariant_indices self._contravariant_indices = args[0]._contravariant_indices return else: - self.array = np.array(args[0], **kwargs) + self.array = np.array(args[0], **kwargs) # type: ignore[call-overload] else: - self.array = np.array(args, **kwargs) + self.array = np.array(args, **kwargs) # type: ignore[call-overload] if tensor_rank is None: tensor_rank = self.rank @@ -271,7 +271,8 @@ def _get_index_mapping(self, index: TensorIndex) -> list[int | None]: # create advanced indices in front for i in advanced_indices: index_mapping.remove(i) - return [None] * b.ndim + index_mapping + new_indices: list[int | None] = [None] * b.ndim + return new_indices + index_mapping else: # replace indices with broadcast shape return index_mapping[:a0] + [None] * b.ndim + index_mapping[a1 + 1 :] @@ -365,7 +366,7 @@ def __eq__(self, other: object) -> bool: if isinstance(other, Tensor): other = other.array try: - return np.allclose(self.array, other, rtol=EQ_TOL_REL, atol=EQ_TOL_ABS) + return np.allclose(self.array, other, rtol=EQ_TOL_REL, atol=EQ_TOL_ABS) # type: ignore[arg-type] except TypeError: return NotImplemented @@ -442,7 +443,7 @@ def from_tensor(cls, tensor: Tensor, **kwargs: Unpack[NDArrayParameters]) -> Sel if tensor.free_indices > 0: return cls(tensor, **kwargs) else: - return cls._element_class(tensor, **kwargs) + return cls._element_class(tensor, **kwargs) # type: ignore[return-value] @classmethod def from_array(cls, array: npt.ArrayLike, **kwargs: Unpack[NDArrayParameters]) -> Self | T: @@ -462,7 +463,7 @@ def from_array(cls, array: npt.ArrayLike, **kwargs: Unpack[NDArrayParameters]) - try: return cls(array, **kwargs) except IncompatibleShapeError: - return cls._element_class(array, **kwargs) + return cls._element_class(array, **kwargs) # type: ignore[return-value] def expand_dims(self, axis: int) -> Self: """Add a new index as a free index. diff --git a/geometer/curve.py b/geometer/curve.py index 8c65d32..0fe7ce2 100644 --- a/geometer/curve.py +++ b/geometer/curve.py @@ -3,7 +3,7 @@ import math from abc import ABC from itertools import combinations -from typing import TYPE_CHECKING, Union, cast +from typing import TYPE_CHECKING, Union, cast, overload import numpy as np import numpy.typing as npt @@ -41,7 +41,7 @@ if TYPE_CHECKING: from typing_extensions import Unpack - from geometer.utils.typing import NDArrayParameters + from geometer.utils.typing import NDArrayParameters, TensorParameters class QuadricTensor(ProjectiveTensor, ABC): @@ -69,7 +69,7 @@ def __init__( matrix: Tensor | npt.ArrayLike, is_dual: bool = False, normalize_matrix: bool = False, - **kwargs: Unpack[NDArrayParameters], + **kwargs: Unpack[TensorParameters], ) -> None: self.is_dual = is_dual @@ -401,6 +401,9 @@ def from_crossratio(cls, cr: float, a: Point, b: Point, c: Point, d: Point) -> C return cls(matrix, normalize_matrix=True) + @overload + def intersect(self, other: LineCollection) -> list[PointCollection]: ... + def intersect(self, other: Line | Conic) -> list[Point]: """Calculates points of intersection with the conic. diff --git a/geometer/operators.py b/geometer/operators.py index 1482be3..b845587 100644 --- a/geometer/operators.py +++ b/geometer/operators.py @@ -157,7 +157,7 @@ def harmonic_set(a: PointTensor, b: PointTensor, c: PointTensor) -> PointTensor: return result -def angle(*args: PointTensor | LineTensor | PlaneTensor) -> npt.NDArray[np.float_]: +def angle(*args: PointTensor | LineTensor | PlaneTensor) -> npt.NDArray[np.float64]: r"""Calculates the (oriented) angle between given points, lines or planes. The function uses the Laguerre formula to calculate angles in two or three-dimensional projective space @@ -271,7 +271,7 @@ def angle_bisectors(l: LineTensor, m: LineTensor) -> tuple[LineTensor, LineTenso return join(o, r), join(o, s) -def _point_dist(p: PointTensor, q: PointTensor) -> npt.NDArray[np.float_]: +def _point_dist(p: PointTensor, q: PointTensor) -> npt.NDArray[np.float64]: if p.dim > 2: x = np.stack(np.broadcast_arrays(p.normalized_array, q.normalized_array), axis=-2) z = x[..., -1] @@ -294,7 +294,7 @@ def _point_dist(p: PointTensor, q: PointTensor) -> npt.NDArray[np.float_]: def dist( p: PointTensor | SubspaceTensor | PolytopeTensor, q: PointTensor | SubspaceTensor | PolytopeTensor -) -> npt.NDArray[np.float_]: +) -> npt.NDArray[np.float64]: r"""Calculates the (euclidean) distance between two objects. Instead of the usual formula for the euclidean distance this function uses diff --git a/geometer/point.py b/geometer/point.py index f207191..15ae63f 100644 --- a/geometer/point.py +++ b/geometer/point.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Literal, Unpack - from geometer.utils.typing import NDArrayParameters, TensorIndex + from geometer.utils.typing import NDArrayParameters, TensorIndex, TensorParameters from geometer.base import ( EQ_TOL_ABS, @@ -28,41 +28,37 @@ @overload def _join_meet_duality( *args: Unpack[tuple[SubspaceTensor, LineTensor]], - intersect_lines: Literal[True], + intersect_lines: Literal[True] = True, check_dependence: bool = True, normalize_result: bool = True, -) -> PointTensor: - ... +) -> PointTensor: ... @overload def _join_meet_duality( *args: Unpack[tuple[LineTensor, SubspaceTensor]], - intersect_lines: Literal[True], + intersect_lines: Literal[True] = True, check_dependence: bool = True, normalize_result: bool = True, -) -> PointTensor: - ... +) -> PointTensor: ... @overload def _join_meet_duality( *args: Unpack[tuple[PointTensor, PointTensor]], - intersect_lines: Literal[True], + intersect_lines: Literal[True] = True, check_dependence: bool = True, normalize_result: bool = True, -) -> LineTensor: - ... +) -> LineTensor: ... @overload def _join_meet_duality( *args: PointTensor | SubspaceTensor, - intersect_lines: Literal[False], + intersect_lines: Literal[False] = False, check_dependence: bool = True, normalize_result: bool = True, -) -> SubspaceTensor: - ... +) -> SubspaceTensor: ... def _join_meet_duality( @@ -190,15 +186,13 @@ def _divide_by_power_of_two(array: np.ndarray, power: int) -> np.ndarray: @overload def join( *args: Unpack[tuple[PointTensor, PointTensor]], _check_dependence: bool = True, _normalize_result: bool = True -) -> LineTensor: - ... +) -> LineTensor: ... @overload def join( *args: PointTensor | SubspaceTensor, _check_dependence: bool = True, _normalize_result: bool = True -) -> SubspaceTensor: - ... +) -> SubspaceTensor: ... def join( @@ -223,22 +217,19 @@ def join( @overload -def meet(*args: LineTensor, _check_dependence: bool = True, _normalize_result: bool = True) -> PointTensor: - ... +def meet(*args: LineTensor, _check_dependence: bool = True, _normalize_result: bool = True) -> PointTensor: ... @overload def meet( *args: Unpack[tuple[SubspaceTensor, LineTensor]], _check_dependence: bool = True, _normalize_result: bool = True -) -> PointTensor: - ... +) -> PointTensor: ... @overload def meet( *args: Unpack[tuple[LineTensor, SubspaceTensor]], _check_dependence: bool = True, _normalize_result: bool = True -) -> PointTensor: - ... +) -> PointTensor: ... def meet( @@ -365,14 +356,12 @@ def __repr__(self) -> str: return f"PointCollection({self.normalized_array.tolist()})" @overload - def join(self, *others: Unpack[tuple[PointTensor, PointTensor]]) -> LineTensor: - ... + def join(self, *others: Unpack[tuple[PointTensor, PointTensor]]) -> LineTensor: ... @overload def join( self, *others: Unpack[tuple[PointTensor, SubspaceTensor]] | Unpack[tuple[SubspaceTensor, PointTensor]] - ) -> SubspaceTensor: - ... + ) -> SubspaceTensor: ... def join(self, *others: PointTensor | LineTensor) -> SubspaceTensor: return join(self, *others) @@ -419,19 +408,15 @@ class SubspaceTensor(ProjectiveTensor, ABC): """ - def __init__( - self, *args: Tensor | npt.ArrayLike, tensor_rank: int = 1, **kwargs: Unpack[NDArrayParameters] - ) -> None: + def __init__(self, *args: Tensor | npt.ArrayLike, tensor_rank: int = 1, **kwargs: Unpack[TensorParameters]) -> None: kwargs.setdefault("covariant", False) super().__init__(*args, tensor_rank=tensor_rank, **kwargs) @overload - def meet(self, other: LineTensor) -> PointTensor: - ... + def meet(self, other: LineTensor) -> PointTensor: ... @overload - def meet(self, other: SubspaceTensor) -> PointTensor | SubspaceTensor: - ... + def meet(self, other: SubspaceTensor) -> PointTensor | SubspaceTensor: ... def meet(self, other: SubspaceTensor) -> PointTensor | SubspaceTensor: return meet(self, other) @@ -883,12 +868,10 @@ def mirror(self, pt: PointTensor) -> PointTensor: return m1.meet(m2) @overload - def perpendicular(self, through: PointTensor) -> LineTensor: - ... + def perpendicular(self, through: PointTensor) -> LineTensor: ... @overload - def perpendicular(self, through: LineTensor) -> PlaneTensor: - ... + def perpendicular(self, through: LineTensor) -> PlaneTensor: ... def perpendicular(self, through: PointTensor | LineTensor) -> LineTensor | PlaneTensor: """Construct the perpendicular lines though the given points or the perpendicular planes through the given lines. diff --git a/geometer/shapes.py b/geometer/shapes.py index 9f94e83..9d4db31 100644 --- a/geometer/shapes.py +++ b/geometer/shapes.py @@ -43,7 +43,7 @@ class PolytopeTensor(PointLikeTensor, ABC): number of vertices and facets. Args: - *args: The polytopes defining the facets ot the polytope. + *args: The polytopes defining the facets of the polytope. pdim: The dimension of the polytope. Default is 0, i.e. an instance of this class is a point. **kwargs: Additional keyword arguments for the constructor of the numpy array as defined in `numpy.array`. @@ -300,7 +300,7 @@ def midpoint(self) -> PointTensor: return harmonic_set(*self.vertices, l) @property - def length(self) -> npt.NDArray[np.float_]: + def length(self) -> npt.NDArray[np.float64]: """The length of the segment.""" return dist(*self.vertices) @@ -544,14 +544,14 @@ def _normalized_projection(self) -> np.ndarray: return self._normalize_array(points) @property - def area(self) -> npt.NDArray[np.float_]: + def area(self) -> npt.NDArray[np.float64]: """The area of the polygon.""" points = self._normalized_projection() a = sum(det(points[..., [0, i, i + 1], :]) for i in range(1, points.shape[-2] - 1)) return 1 / 2 * np.abs(a) @property - def angles(self) -> list[npt.NDArray[np.float_]]: + def angles(self) -> list[npt.NDArray[np.float64]]: """The interior angles of the polygon.""" result = [] a = cast(SegmentTensor, self.edges[-1]) @@ -609,7 +609,7 @@ def __init__( super().__init__(*vertices, **kwargs) @property - def radius(self) -> npt.NDArray[np.float_]: + def radius(self) -> npt.NDArray[np.float64]: """The circumradius of the regular polygon.""" return dist(self.center, self.vertices[0]) @@ -619,7 +619,7 @@ def center(self) -> Point: return Point(*np.sum(self.normalized_array[:, :-1], axis=0)) @property - def inradius(self) -> npt.NDArray[np.float_]: + def inradius(self) -> npt.NDArray[np.float64]: """The inradius of the regular polygon.""" return dist(self.center, cast(SegmentTensor, self.edges[0]).midpoint) @@ -708,7 +708,7 @@ def edges(self) -> list[SegmentTensor]: return list(distinct(Segment(result[idx], copy=False) for idx in np.ndindex(*self.shape[:2]))) @property - def area(self) -> npt.NDArray[np.float_] | np.float_: + def area(self) -> npt.NDArray[np.float64] | np.float64: """The surface area of the polyhedron.""" return np.sum(self.faces.area) diff --git a/geometer/transformation.py b/geometer/transformation.py index c54c2fe..8e6b333 100644 --- a/geometer/transformation.py +++ b/geometer/transformation.py @@ -16,7 +16,7 @@ TensorDiagram, ) from geometer.exceptions import NoIncidence -from geometer.point import LineTensor, Point, PointTensor, Subspace, infty_hyperplane +from geometer.point import LineTensor, Point, Subspace, infty_hyperplane from geometer.utils import inv, matmul, outer if TYPE_CHECKING: @@ -27,13 +27,11 @@ @overload -def identity(dim: int, collection_dims: Literal[None] = None) -> Transformation: - ... +def identity(dim: int, collection_dims: Literal[None] = None) -> Transformation: ... @overload -def identity(dim: int, collection_dims: tuple[int, ...] | None = None) -> TransformationCollection: - ... +def identity(dim: int, collection_dims: tuple[int, ...] | None = None) -> TransformationCollection: ... def identity(dim: int, collection_dims: tuple[int, ...] | None = None) -> TransformationTensor: @@ -89,7 +87,7 @@ def affine_transform(matrix: npt.ArrayLike | None = None, offset: npt.ArrayLike return Transformation(result, copy=False) -def rotation(angle: float | np.float_, axis: Point | None = None) -> Transformation: +def rotation(angle: float | np.float64, axis: Point | None = None) -> Transformation: """Returns a projective transformation that represents a rotation by the specified angle (and axis). Args: @@ -199,8 +197,70 @@ def __init__(self, *args: Tensor | npt.ArrayLike, **kwargs: Unpack[NDArrayParame def __apply__(self, transformation: TransformationTensor) -> TransformationTensor: return TransformationCollection.from_array(matmul(transformation.array, self.array)) + T = TypeVar("T", bound=Tensor) + + def apply(self, other: T) -> T: + """Apply the transformation to another object. + + Args: + other: The object to apply the transformation to. + + Returns: + The result of applying this transformation to the supplied object. + + """ + if hasattr(other, "__apply__"): + return other.__apply__(self) + raise NotImplementedError(f"Object of type {type(other)} cannot be transformed.") + + @overload + def __mul__(self, other: TransformationTensor) -> TransformationTensor: ... + + @overload + def __mul__(self, other: T) -> T: ... + + @overload + def __mul__(self, other: Tensor | npt.ArrayLike) -> Tensor: ... + + def __mul__(self, other: Tensor | npt.ArrayLike) -> Tensor: + try: + return self.apply(other) + except NotImplementedError: + return super().__mul__(other) + + def __pow__(self, power: int, modulo: int | None = None) -> Tensor: + if power == 0: + if self.free_indices == 0: + return identity(self.dim) + return identity(self.dim, self.shape[: self.free_indices]) + if power < 0: + return self.inverse().__pow__(-power, modulo) + + result = super().__pow__(power, modulo) + return type(self)(result, copy=False) + + def __getitem__(self, index: TensorIndex) -> Tensor | np.generic: + result = super().__getitem__(index) + + if not isinstance(result, Tensor) or result.tensor_shape != (1, 1): + return result + + return TransformationCollection.from_tensor(result) + + def inverse(self) -> TransformationTensor: + """Calculates the inverse projective transformation. + + Returns: + The inverse transformation. + + """ + return type(self)(inv(self.array), copy=False) + + +class Transformation(TransformationTensor, BoundTensor): + @classmethod - def from_points(cls, *args: tuple[PointTensor, PointTensor]) -> TransformationTensor: + def from_points(cls, *args: tuple[Point, Point]) -> Transformation: """Constructs a projective transformation in n-dimensional projective space from the image of n + 2 points in general position. For two dimensional transformations, 4 pairs of points are required, of which no three points are collinear. @@ -229,7 +289,7 @@ def from_points(cls, *args: tuple[PointTensor, PointTensor]) -> TransformationTe @classmethod def from_points_and_conics( - cls, points1: Sequence[PointTensor], points2: Sequence[PointTensor], conic1: Conic, conic2: Conic + cls, points1: Sequence[Point], points2: Sequence[Point], conic1: Conic, conic2: Conic ) -> TransformationTensor: """Constructs a projective transformation from two conics and the image of pairs of 3 points on the conics. @@ -272,72 +332,6 @@ def from_points_and_conics( return cls.from_points((a1, a2), (b1, b2), (c1, c2), (d1, d2)) - T = TypeVar("T", bound=Tensor) - - def apply(self, other: T) -> T: - """Apply the transformation to another object. - - Args: - other: The object to apply the transformation to. - - Returns: - The result of applying this transformation to the supplied object. - - """ - if hasattr(other, "__apply__"): - return other.__apply__(self) - raise NotImplementedError(f"Object of type {type(other)} cannot be transformed.") - - @overload - def __mul__(self, other: TransformationTensor) -> TransformationTensor: - ... - - @overload - def __mul__(self, other: T) -> T: - ... - - @overload - def __mul__(self, other: Tensor | npt.ArrayLike) -> Tensor: - ... - - def __mul__(self, other: Tensor | npt.ArrayLike) -> Tensor: - try: - return self.apply(other) - except NotImplementedError: - return super().__mul__(other) - - def __pow__(self, power: int, modulo: int | None = None) -> Tensor: - if power == 0: - if self.free_indices == 0: - return identity(self.dim) - return identity(self.dim, self.shape[: self.free_indices]) - if power < 0: - return self.inverse().__pow__(-power, modulo) - - result = super().__pow__(power, modulo) - return type(self)(result, copy=False) - - def __getitem__(self, index: TensorIndex) -> Tensor | np.generic: - result = super().__getitem__(index) - - if not isinstance(result, Tensor) or result.tensor_shape != (1, 1): - return result - - return TransformationCollection.from_tensor(result) - - def inverse(self) -> TransformationTensor: - """Calculates the inverse projective transformation. - - Returns: - The inverse transformation. - - """ - return type(self)(inv(self.array), copy=False) - - -class Transformation(TransformationTensor, BoundTensor): - pass - class TransformationCollection(TransformationTensor, TensorCollection[Transformation]): _element_class = Transformation diff --git a/geometer/utils/math.py b/geometer/utils/math.py index 5419081..72acc0f 100644 --- a/geometer/utils/math.py +++ b/geometer/utils/math.py @@ -378,7 +378,7 @@ def matmul( a = np.swapaxes(a, -1, -2) if transpose_b: b = np.swapaxes(b, -1, -2) - return np.matmul(a, b, out=out, **kwargs) + return np.matmul(a, b, out=out, **kwargs) # type: ignore[call-overload] def matvec( @@ -496,4 +496,4 @@ def outer( """ a, b = np.asarray(a), np.asarray(b) - return np.multiply(a[..., None], b[..., None, :], out=out, **kwargs) + return np.multiply(a[..., None], b[..., None, :], out=out, **kwargs) # type: ignore[call-overload] diff --git a/geometer/utils/typing.py b/geometer/utils/typing.py index bc47ce2..56bb919 100644 --- a/geometer/utils/typing.py +++ b/geometer/utils/typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from numbers import Number from typing import TYPE_CHECKING, Literal, TypedDict, Union @@ -25,6 +25,10 @@ class NDArrayParameters(TypedDict, total=False): like: npt.ArrayLike +class TensorParameters(NDArrayParameters): + covariant: bool | Iterable[int] + + NumericalDType: TypeAlias = Union[np.number, np.bool_] NumericalArray: TypeAlias = npt.NDArray[NumericalDType] NumericalScalar: TypeAlias = Union[Number, np.number, np.bool_, NumericalArray] # TODO: restrict array shape diff --git a/pyproject.toml b/pyproject.toml index 863b396..68f52dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,12 +58,17 @@ tests = "coverage run -m pytest {args:tests}" rtd-docs = "sphinx-build docs/ _readthedocs/html/" [tool.black] -target-version = ["py39"] +target-version = ["py39", "py310", "py311", "py312"] line-length = 120 [tool.ruff] target-version = "py39" line-length = 120 +exclude = [ + "docs" +] + +[tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings @@ -105,21 +110,18 @@ ignore = [ "D417", # TODO: fix ] unfixable = [] -exclude = [ - "docs" -] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] "tests/**" = ["D"] -[tool.ruff.isort] +[tool.ruff.lint.isort] known-first-party = ["geometer"] -[tool.ruff.flake8-tidy-imports] +[tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "google" [tool.mypy] @@ -142,3 +144,6 @@ exclude_also = [ "@(abc\\.)?abstractmethod", "@(typing(_extensions)?\\.)?overload", ] + +[tool.codespell] +ignore-words-list = "nin"