diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..82532c6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +# end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 +charset = utf-8 + +[*.{bat,cmd,ps1}] +end_of_line = crlf + +[*.sh] +end_of_line = lf + +[LICENSE] +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d70ecd2..d840412 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,19 +9,15 @@ on: - main jobs: - check: - name: Black and pytest + ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + - run: uv python install 3.14 + - run: uv run pytest --cov --cov-branch --cov-report=xml + - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: - python-version: "3.14" - enable-cache: true - - name: Sync - run: uv sync --locked --group dev - - name: Black - run: uv run black --check . - - name: Tests - run: uv run pytest + token: ${{ secrets.CODECOV_TOKEN }} + - run: uv run ruff format --check + - run: uv build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index acd02ba..8cf9ea7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,13 +1,12 @@ -name: Publish CwMath Package +name: Publish cwapi3d Package on: push: - tags: - - "v*" + branches: + - "main" jobs: build-and-publish: - name: Build and Publish runs-on: ubuntu-latest environment: name: pypi @@ -16,12 +15,19 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@v4 - - name: Install uv + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dorny/paths-filter@vceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 + id: changes + with: + filters: | + src: + - 'src/**' + - 'pyproject.toml' + - if: steps.changes.outputs.src == 'true' uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - - name: Install Python + - if: steps.changes.outputs.src == 'true' run: uv python install 3.14 - - name: Build + - if: steps.changes.outputs.src == 'true' run: uv build - - name: Publish - run: uv publish + - if: steps.changes.outputs.src == 'true' + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/.pip-tools.toml b/.pip-tools.toml deleted file mode 100644 index 9784ab5..0000000 --- a/.pip-tools.toml +++ /dev/null @@ -1,4 +0,0 @@ -[tool.pip-tools] -generate-hashes = true -allow-unsafe = true -strip-extras = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..35fcf57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-illegal-windows-names + - id: end-of-file-fixer + - id: fix-byte-order-marker + - id: forbid-submodules + - id: no-commit-to-branch + - id: trailing-whitespace diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/GUIDELINES.md b/GUIDELINES.md index 990f4de..2acf46a 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -5,17 +5,17 @@ * use meaningful and appropriate naming -Please follow the style guide below when adding functionalities. +Please follow the style guide below when adding functionalities. ```python - # imports import math import sys from myclass import MyClass + # example function -def add_one(number:int) -> int: +def add_one(number: int) -> int: """Increase number by one. Author: John Doe @@ -32,15 +32,12 @@ def add_one(number:int) -> int: # Aligned with opening delimiter. -foo = long_function_name(var_one, var_two, - var_three, var_four) +foo = long_function_name(var_one, var_two, var_three, var_four) if foo == 'blah': do_blah_thing() - + do_one() do_two() do_three() - ``` - diff --git a/LICENSE b/LICENSE index fdaae03..c0cbac8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Cadwork +Copyright (c) 2026 Cadwork Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/style_guide.md b/docs/style_guide.md index 990f4de..2acf46a 100644 --- a/docs/style_guide.md +++ b/docs/style_guide.md @@ -5,17 +5,17 @@ * use meaningful and appropriate naming -Please follow the style guide below when adding functionalities. +Please follow the style guide below when adding functionalities. ```python - # imports import math import sys from myclass import MyClass + # example function -def add_one(number:int) -> int: +def add_one(number: int) -> int: """Increase number by one. Author: John Doe @@ -32,15 +32,12 @@ def add_one(number:int) -> int: # Aligned with opening delimiter. -foo = long_function_name(var_one, var_two, - var_three, var_four) +foo = long_function_name(var_one, var_two, var_three, var_four) if foo == 'blah': do_blah_thing() - + do_one() do_two() do_three() - ``` - diff --git a/mkdocs.yml b/mkdocs.yml index d6c99d7..ef19aa6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,7 +3,7 @@ site_name: Cadwork Math Utilities repo_name: cwapi3d/cwmath repo_url: https://github.com/cwapi3d/cwmath -copyright: Copyright 2024 Cadwork +copyright: Copyright 2026 Cadwork site_url: https://python.cadwork.dev/ @@ -11,7 +11,7 @@ theme: name: material logo: cw_logo.svg favicon: cw_logo.svg - version: V30 + version: V2026 palette: - scheme: default toggle: @@ -75,6 +75,7 @@ extra_css: plugins: - search + - autorefs - mkdocstrings: handlers: python: diff --git a/pyproject.toml b/pyproject.toml index 82e27e2..f9eef94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,43 +1,45 @@ [project] name = "cwmath" -version = "1.0.0" +version = "1.0.1" + authors = [{ name = "Cadwork", email = "it@cadwork.ca" }] requires-python = ">=3.14" description = "Cadwork Math Utilities" readme = "README.md" -license = { file = "LICENSE" } -keywords = ["cadwork"] +license-files = [ "LICENSE" ] +keywords = [ "cadwork" ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Plugins", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", ] -dependencies = [] [project.urls] Homepage = "https://github.com/cwapi3d/cwmath" -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - [dependency-groups] dev = [ - "black", - "mkdocs", - "mkdocs-material", - "mkdocstrings[python]", - "pytest", + "black>=26.5.1", + "mkdocs>=1.6.1", + "mkdocs-autorefs>=1.4.4", + "mkdocs-material>=9.7.7", + "mkdocstrings[python]>=1.0.6", + "pre-commit>=4.6.2", + "pytest>=9.1.1", + "pytest-cov>=7.1.0", + "ruff>=0.16.3", ] -[tool.black] -line-length = 88 -target-version = ["py314"] +[tool.ruff] +line-length = 120 -[tool.pytest.ini_options] -testpaths = ["tests"] +[tool.ruff.format] +quote-style = "single" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/src/cwmath/__init__.py b/src/cwmath/__init__.py index cb0a75a..e3c634d 100644 --- a/src/cwmath/__init__.py +++ b/src/cwmath/__init__.py @@ -11,17 +11,17 @@ from cwmath.vec3 import Vec3 __all__ = [ - "ABS_TOL", - "REL_TOL", - "Frame3", - "Line3", - "Plane3", - "Point2", - "Point3", - "Vec2", - "Vec3", - "is_close", - "project_point_on_line", - "project_point_on_plane", - "to_point_3d", + 'ABS_TOL', + 'REL_TOL', + 'Frame3', + 'Line3', + 'Plane3', + 'Point2', + 'Point3', + 'Vec2', + 'Vec3', + 'is_close', + 'project_point_on_line', + 'project_point_on_plane', + 'to_point_3d', ] diff --git a/src/cwmath/cadwork.py b/src/cwmath/cadwork.py index a14ab48..631961c 100644 --- a/src/cwmath/cadwork.py +++ b/src/cwmath/cadwork.py @@ -29,14 +29,13 @@ def to_point_3d( is imported only at this call site. """ if not isinstance(value, (Point3, Vec3)): - raise TypeError("to_point_3d expects a Point3 or Vec3") + raise TypeError('to_point_3d expects a Point3 or Vec3') if factory is None: try: import cadwork as host except ImportError as exc: raise CadworkNotAvailableError( - "cadwork is not available; pass factory= to construct " - "a point_3d outside the cadwork host" + 'cadwork is not available; pass factory= to construct a point_3d outside the cadwork host' ) from exc factory = host return factory.point_3d(value.x, value.y, value.z) diff --git a/src/cwmath/frame3.py b/src/cwmath/frame3.py index 945bcb8..8287946 100644 --- a/src/cwmath/frame3.py +++ b/src/cwmath/frame3.py @@ -12,12 +12,12 @@ def _require_point3(value: object, name: str) -> None: if not isinstance(value, Point3): - raise TypeError(f"{name} must be a Point3") + raise TypeError(f'{name} must be a Point3') def _require_vec3(value: object, name: str) -> None: if not isinstance(value, Vec3): - raise TypeError(f"{name} must be a Vec3") + raise TypeError(f'{name} must be a Vec3') def _is_unit(axis: Vec3) -> bool: @@ -30,11 +30,11 @@ def _is_orthogonal(a: Vec3, b: Vec3) -> bool: def _require_orthonormal_right_handed(x: Vec3, y: Vec3, z: Vec3) -> None: if not (_is_unit(x) and _is_unit(y) and _is_unit(z)): - raise ValueError("frame axes must be unit length") + raise ValueError('frame axes must be unit length') if not (_is_orthogonal(x, y) and _is_orthogonal(y, z) and _is_orthogonal(z, x)): - raise ValueError("frame axes must be mutually orthogonal") + raise ValueError('frame axes must be mutually orthogonal') if not x.cross(y).is_close(z): - raise ValueError("frame axes must form a right-handed basis") + raise ValueError('frame axes must form a right-handed basis') @dataclass(frozen=True, slots=True) @@ -47,10 +47,10 @@ class Frame3: z_axis: Vec3 def __post_init__(self) -> None: - _require_point3(self.origin, "origin") - _require_vec3(self.x_axis, "x_axis") - _require_vec3(self.y_axis, "y_axis") - _require_vec3(self.z_axis, "z_axis") + _require_point3(self.origin, 'origin') + _require_vec3(self.x_axis, 'x_axis') + _require_vec3(self.y_axis, 'y_axis') + _require_vec3(self.z_axis, 'z_axis') _require_orthonormal_right_handed(self.x_axis, self.y_axis, self.z_axis) @classmethod @@ -65,30 +65,30 @@ def from_origin_xy(cls, origin: Point3, x: Vec3, y: Vec3) -> Frame3: ``z = x × y``; then orthonormalize x, then y, then z. A zero or parallel pair is degenerate and raises ``ValueError``. """ - _require_point3(origin, "origin") - _require_vec3(x, "x") - _require_vec3(y, "y") + _require_point3(origin, 'origin') + _require_vec3(x, 'x') + _require_vec3(y, 'y') z = x.cross(y) if z.is_close(Vec3.zero()): - raise ValueError("from_origin_xy: x and y are degenerate") + raise ValueError('from_origin_xy: x and y are degenerate') try: x_hat = x.normalized() except ValueError: - raise ValueError("from_origin_xy: x and y are degenerate") from None + raise ValueError('from_origin_xy: x and y are degenerate') from None y_proj = y - x_hat * y.dot(x_hat) try: y_hat = y_proj.normalized() except ValueError: - raise ValueError("from_origin_xy: x and y are degenerate") from None + raise ValueError('from_origin_xy: x and y are degenerate') from None z_proj = z - x_hat * z.dot(x_hat) - y_hat * z.dot(y_hat) try: z_hat = z_proj.normalized() except ValueError: - raise ValueError("from_origin_xy: x and y are degenerate") from None + raise ValueError('from_origin_xy: x and y are degenerate') from None return cls(origin=origin, x_axis=x_hat, y_axis=y_hat, z_axis=z_hat) @@ -100,15 +100,10 @@ def to_world(self, value: Vec3) -> Vec3: ... def to_world(self, value: Point3 | Vec3) -> Point3 | Vec3: if isinstance(value, Point3): - return ( - self.origin - + self.x_axis * value.x - + self.y_axis * value.y - + self.z_axis * value.z - ) + return self.origin + self.x_axis * value.x + self.y_axis * value.y + self.z_axis * value.z if isinstance(value, Vec3): return self.x_axis * value.x + self.y_axis * value.y + self.z_axis * value.z - raise TypeError("to_world expects a Point3 or Vec3") + raise TypeError('to_world expects a Point3 or Vec3') @overload def to_local(self, value: Point3) -> Point3: ... @@ -126,4 +121,4 @@ def to_local(self, value: Point3 | Vec3) -> Point3 | Vec3: value.dot(self.y_axis), value.dot(self.z_axis), ) - raise TypeError("to_local expects a Point3 or Vec3") + raise TypeError('to_local expects a Point3 or Vec3') diff --git a/src/cwmath/line3.py b/src/cwmath/line3.py index f5b4cab..3be29ad 100644 --- a/src/cwmath/line3.py +++ b/src/cwmath/line3.py @@ -10,12 +10,12 @@ def _require_point3(value: object, name: str) -> None: if not isinstance(value, Point3): - raise TypeError(f"{name} must be a Point3") + raise TypeError(f'{name} must be a Point3') def _require_vec3(value: object, name: str) -> None: if not isinstance(value, Vec3): - raise TypeError(f"{name} must be a Vec3") + raise TypeError(f'{name} must be a Vec3') @dataclass(frozen=True, slots=True) @@ -26,13 +26,13 @@ class Line3: direction: Vec3 def __post_init__(self) -> None: - _require_point3(self.origin, "origin") - _require_vec3(self.direction, "direction") + _require_point3(self.origin, 'origin') + _require_vec3(self.direction, 'direction') try: unit = self.direction.normalized() except ValueError: - raise ValueError("line direction must be non-zero") from None - object.__setattr__(self, "direction", unit) + raise ValueError('line direction must be non-zero') from None + object.__setattr__(self, 'direction', unit) @classmethod def from_origin_direction(cls, origin: Point3, direction: Vec3) -> Line3: @@ -42,17 +42,17 @@ def from_origin_direction(cls, origin: Point3, direction: Vec3) -> Line3: @classmethod def from_points(cls, a: Point3, b: Point3) -> Line3: """Build a line through two distinct points, directed ``a → b``.""" - _require_point3(a, "a") - _require_point3(b, "b") + _require_point3(a, 'a') + _require_point3(b, 'b') try: direction = (b - a).normalized() except ValueError: - raise ValueError("from_points: points are coincident") from None + raise ValueError('from_points: points are coincident') from None return cls(origin=a, direction=direction) def parameter(self, point: Point3) -> float: """Return ``t`` such that ``origin + t * direction`` is the foot of ``point``.""" - _require_point3(point, "point") + _require_point3(point, 'point') return (point - self.origin).dot(self.direction) def point_at(self, t: float) -> Point3: diff --git a/src/cwmath/plane3.py b/src/cwmath/plane3.py index b5265c4..6d95d93 100644 --- a/src/cwmath/plane3.py +++ b/src/cwmath/plane3.py @@ -11,12 +11,12 @@ def _require_point3(value: object, name: str) -> None: if not isinstance(value, Point3): - raise TypeError(f"{name} must be a Point3") + raise TypeError(f'{name} must be a Point3') def _require_vec3(value: object, name: str) -> None: if not isinstance(value, Vec3): - raise TypeError(f"{name} must be a Vec3") + raise TypeError(f'{name} must be a Vec3') @dataclass(frozen=True, slots=True) @@ -27,13 +27,13 @@ class Plane3: normal: Vec3 def __post_init__(self) -> None: - _require_point3(self.point, "point") - _require_vec3(self.normal, "normal") + _require_point3(self.point, 'point') + _require_vec3(self.normal, 'normal') try: unit = self.normal.normalized() except ValueError: - raise ValueError("plane normal must be non-zero") from None - object.__setattr__(self, "normal", unit) + raise ValueError('plane normal must be non-zero') from None + object.__setattr__(self, 'normal', unit) @classmethod def from_point_normal(cls, point: Point3, normal: Vec3) -> Plane3: @@ -41,7 +41,7 @@ def from_point_normal(cls, point: Point3, normal: Vec3) -> Plane3: return cls(point=point, normal=normal) def signed_distance(self, point: Point3) -> float: - _require_point3(point, "point") + _require_point3(point, 'point') return (point - self.point).dot(self.normal) def distance(self, point: Point3) -> float: @@ -52,16 +52,12 @@ def contains(self, point: Point3) -> bool: def project(self, point: Point3) -> Point3: """Return the foot of ``point`` on this plane.""" - _require_point3(point, "point") + _require_point3(point, 'point') return point - self.normal * self.signed_distance(point) def as_abcd(self) -> tuple[float, float, float, float]: """Return the implicit coefficients ``ax + by + cz + d = 0``.""" - d = -( - self.normal.x * self.point.x - + self.normal.y * self.point.y - + self.normal.z * self.point.z - ) + d = -(self.normal.x * self.point.x + self.normal.y * self.point.y + self.normal.z * self.point.z) return (self.normal.x, self.normal.y, self.normal.z, d) diff --git a/src/cwmath/point2.py b/src/cwmath/point2.py index 1b008a6..0ae9aeb 100644 --- a/src/cwmath/point2.py +++ b/src/cwmath/point2.py @@ -28,8 +28,8 @@ class Point2: y: float def __post_init__(self) -> None: - object.__setattr__(self, "x", float(self.x)) - object.__setattr__(self, "y", float(self.y)) + object.__setattr__(self, 'x', float(self.x)) + object.__setattr__(self, 'y', float(self.y)) @classmethod def from_xy(cls, value: Xy) -> Self: @@ -47,9 +47,9 @@ def is_close( ) -> bool: if not isinstance(other, Point2): return False - return close_scalars( - self.x, other.x, abs_tol=abs_tol, rel_tol=rel_tol - ) and close_scalars(self.y, other.y, abs_tol=abs_tol, rel_tol=rel_tol) + return close_scalars(self.x, other.x, abs_tol=abs_tol, rel_tol=rel_tol) and close_scalars( + self.y, other.y, abs_tol=abs_tol, rel_tol=rel_tol + ) def with_x(self, x: float) -> Point2: return Point2(x, self.y) @@ -59,12 +59,12 @@ def with_y(self, y: float) -> Point2: def distance(self, other: Point2) -> float: if not isinstance(other, Point2): - raise TypeError("distance expects a Point2") + raise TypeError('distance expects a Point2') return (other - self).magnitude() def lerp(self, other: Point2, t: float) -> Point2: if not isinstance(other, Point2): - raise TypeError("lerp expects a Point2") + raise TypeError('lerp expects a Point2') return self + (other - self) * t def __add__(self, other: object) -> Point2: diff --git a/src/cwmath/point3.py b/src/cwmath/point3.py index 28a7379..7eaf7c5 100644 --- a/src/cwmath/point3.py +++ b/src/cwmath/point3.py @@ -32,9 +32,9 @@ class Point3: z: float def __post_init__(self) -> None: - object.__setattr__(self, "x", float(self.x)) - object.__setattr__(self, "y", float(self.y)) - object.__setattr__(self, "z", float(self.z)) + object.__setattr__(self, 'x', float(self.x)) + object.__setattr__(self, 'y', float(self.y)) + object.__setattr__(self, 'z', float(self.z)) @classmethod def from_xyz(cls, value: Xyz) -> Self: @@ -69,12 +69,12 @@ def with_z(self, z: float) -> Point3: def distance(self, other: Point3) -> float: if not isinstance(other, Point3): - raise TypeError("distance expects a Point3") + raise TypeError('distance expects a Point3') return (other - self).magnitude() def lerp(self, other: Point3, t: float) -> Point3: if not isinstance(other, Point3): - raise TypeError("lerp expects a Point3") + raise TypeError('lerp expects a Point3') return self + (other - self) * t def __add__(self, other: object) -> Point3: diff --git a/src/cwmath/vec2.py b/src/cwmath/vec2.py index a6d4ec6..69f2bc9 100644 --- a/src/cwmath/vec2.py +++ b/src/cwmath/vec2.py @@ -21,8 +21,8 @@ class Vec2: y: float def __post_init__(self) -> None: - object.__setattr__(self, "x", float(self.x)) - object.__setattr__(self, "y", float(self.y)) + object.__setattr__(self, 'x', float(self.x)) + object.__setattr__(self, 'y', float(self.y)) @classmethod def zero(cls) -> Self: @@ -44,9 +44,9 @@ def is_close( ) -> bool: if not isinstance(other, Vec2): return False - return close_scalars( - self.x, other.x, abs_tol=abs_tol, rel_tol=rel_tol - ) and close_scalars(self.y, other.y, abs_tol=abs_tol, rel_tol=rel_tol) + return close_scalars(self.x, other.x, abs_tol=abs_tol, rel_tol=rel_tol) and close_scalars( + self.y, other.y, abs_tol=abs_tol, rel_tol=rel_tol + ) def with_x(self, x: float) -> Vec2: return Vec2(x, self.y) @@ -56,13 +56,13 @@ def with_y(self, y: float) -> Vec2: def dot(self, other: Vec2) -> float: if not isinstance(other, Vec2): - raise TypeError("dot expects a Vec2") + raise TypeError('dot expects a Vec2') return self.x * other.x + self.y * other.y def cross(self, other: Vec2) -> float: """Signed parallelogram area of ``self`` × ``other``.""" if not isinstance(other, Vec2): - raise TypeError("cross expects a Vec2") + raise TypeError('cross expects a Vec2') return self.x * other.y - self.y * other.x def magnitude(self) -> float: @@ -71,12 +71,12 @@ def magnitude(self) -> float: def normalized(self) -> Vec2: mag = self.magnitude() if mag == 0.0: - raise ValueError("cannot normalize a zero vector") + raise ValueError('cannot normalize a zero vector') return self / mag def angle_to(self, other: Vec2) -> float: if not isinstance(other, Vec2): - raise TypeError("angle_to expects a Vec2") + raise TypeError('angle_to expects a Vec2') cosine = self.normalized().dot(other.normalized()) return math.acos(max(-1.0, min(1.0, cosine))) diff --git a/src/cwmath/vec3.py b/src/cwmath/vec3.py index 0d15812..244dc3d 100644 --- a/src/cwmath/vec3.py +++ b/src/cwmath/vec3.py @@ -22,9 +22,9 @@ class Vec3: z: float def __post_init__(self) -> None: - object.__setattr__(self, "x", float(self.x)) - object.__setattr__(self, "y", float(self.y)) - object.__setattr__(self, "z", float(self.z)) + object.__setattr__(self, 'x', float(self.x)) + object.__setattr__(self, 'y', float(self.y)) + object.__setattr__(self, 'z', float(self.z)) @classmethod def zero(cls) -> Self: @@ -63,12 +63,12 @@ def with_z(self, z: float) -> Vec3: def dot(self, other: Vec3) -> float: if not isinstance(other, Vec3): - raise TypeError("dot expects a Vec3") + raise TypeError('dot expects a Vec3') return self.x * other.x + self.y * other.y + self.z * other.z def cross(self, other: Vec3) -> Vec3: if not isinstance(other, Vec3): - raise TypeError("cross expects a Vec3") + raise TypeError('cross expects a Vec3') return Vec3( self.y * other.z - self.z * other.y, self.z * other.x - self.x * other.z, @@ -81,12 +81,12 @@ def magnitude(self) -> float: def normalized(self) -> Vec3: mag = self.magnitude() if mag == 0.0: - raise ValueError("cannot normalize a zero vector") + raise ValueError('cannot normalize a zero vector') return self / mag def angle_to(self, other: Vec3) -> float: if not isinstance(other, Vec3): - raise TypeError("angle_to expects a Vec3") + raise TypeError('angle_to expects a Vec3') cosine = self.normalized().dot(other.normalized()) return math.acos(max(-1.0, min(1.0, cosine))) diff --git a/tests/test_cadwork.py b/tests/test_cadwork.py index b5ff4f1..7487bbf 100644 --- a/tests/test_cadwork.py +++ b/tests/test_cadwork.py @@ -33,7 +33,7 @@ def test_to_point_3d_with_factory_vec3() -> None: def test_to_point_3d_not_available_without_host( monkeypatch: pytest.MonkeyPatch, ) -> None: - monkeypatch.setitem(sys.modules, "cadwork", None) + monkeypatch.setitem(sys.modules, 'cadwork', None) with pytest.raises(CadworkNotAvailableError): to_point_3d(Point3(1, 2, 3)) diff --git a/tests/test_exports.py b/tests/test_exports.py index 3385b5e..2c28185 100644 --- a/tests/test_exports.py +++ b/tests/test_exports.py @@ -5,22 +5,22 @@ import cwmath EXPECTED_EXPORTS = { - "ABS_TOL", - "REL_TOL", - "Frame3", - "Line3", - "Plane3", - "Point2", - "Point3", - "Vec2", - "Vec3", - "is_close", - "project_point_on_line", - "project_point_on_plane", - "to_point_3d", + 'ABS_TOL', + 'REL_TOL', + 'Frame3', + 'Line3', + 'Plane3', + 'Point2', + 'Point3', + 'Vec2', + 'Vec3', + 'is_close', + 'project_point_on_line', + 'project_point_on_plane', + 'to_point_3d', } -LEGACY_NAMES = ("CwVector3d", "CwPlane3d", "cwexamples") +LEGACY_NAMES = ('CwVector3d', 'CwPlane3d', 'cwexamples') def test_top_level_import_list() -> None: @@ -60,4 +60,4 @@ def test_no_legacy_names_or_version() -> None: for name in LEGACY_NAMES: assert not hasattr(cwmath, name) assert name not in cwmath.__all__ - assert importlib.metadata.version("cwmath") == "1.0.0" + assert importlib.metadata.version('cwmath') == '1.0.1' diff --git a/tests/test_packaging.py b/tests/test_packaging.py index efa043e..0cc890c 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -9,24 +9,20 @@ def test_wheel_excludes_tests(tmp_path: Path) -> None: subprocess.run( - ["uv", "build", "--wheel", "--out-dir", str(tmp_path)], + ['uv', 'build', '--wheel', '--out-dir', str(tmp_path)], check=True, cwd=ROOT, ) - wheels = list(tmp_path.glob("cwmath-*.whl")) + wheels = list(tmp_path.glob('cwmath-*.whl')) assert len(wheels) == 1 names = zipfile.ZipFile(wheels[0]).namelist() - assert any(name.startswith("cwmath/") for name in names) - assert any( - name == "cwmath/py.typed" or name.endswith("/cwmath/py.typed") for name in names - ) - assert not any( - name.startswith("cwmath/tests/") or name.startswith("tests/") for name in names - ) + assert any(name.startswith('cwmath/') for name in names) + assert any(name == 'cwmath/py.typed' or name.endswith('/cwmath/py.typed') for name in names) + assert not any(name.startswith('cwmath/tests/') or name.startswith('tests/') for name in names) lowered = [name.lower() for name in names] - assert not any("cwvector3d" in name or "cwplane3d" in name for name in lowered) + assert not any('cwvector3d' in name or 'cwplane3d' in name for name in lowered) def test_kept_files_present() -> None: - assert (ROOT / "CODEOWNERS").is_file() - assert (ROOT / "LICENSE").is_file() + assert (ROOT / 'CODEOWNERS').is_file() + assert (ROOT / 'LICENSE').is_file() diff --git a/tests/test_readme_example.py b/tests/test_readme_example.py index 010872d..288bfa9 100644 --- a/tests/test_readme_example.py +++ b/tests/test_readme_example.py @@ -8,29 +8,29 @@ from cwmath import Point3 from fakes import FakeCadwork, FakePoint3d -README = Path(__file__).resolve().parents[1] / "README.md" +README = Path(__file__).resolve().parents[1] / 'README.md' def _readme_python_example() -> str: - text = README.read_text(encoding="utf-8") - match = re.search(r"```python\n(.*?)```", text, re.S) - assert match is not None, "README is missing a python example fence" + text = README.read_text(encoding='utf-8') + match = re.search(r'```python\n(.*?)```', text, re.S) + assert match is not None, 'README is missing a python example fence' return match.group(1) def test_readme_example_converts_frames_and_projects() -> None: - cadwork = types.ModuleType("cadwork") + cadwork = types.ModuleType('cadwork') cadwork.point_3d = FakeCadwork.point_3d # type: ignore[attr-defined] - sys.modules["cadwork"] = cadwork + sys.modules['cadwork'] = cadwork try: namespace: dict[str, object] = {} exec(_readme_python_example(), namespace) # noqa: S102 finally: - sys.modules.pop("cadwork", None) + sys.modules.pop('cadwork', None) - world = namespace["world"] - foot = namespace["foot"] - result = namespace["result"] + world = namespace['world'] + foot = namespace['foot'] + result = namespace['result'] assert isinstance(world, Point3) assert isinstance(foot, Point3) assert world.is_close(Point3(1050.0, 2025.0, 10.0)) diff --git a/tests/test_tolerance.py b/tests/test_tolerance.py index a76ddf8..93ecc7a 100644 --- a/tests/test_tolerance.py +++ b/tests/test_tolerance.py @@ -5,7 +5,7 @@ def test_package_imports() -> None: - assert cwmath.__name__ == "cwmath" + assert cwmath.__name__ == 'cwmath' def test_default_tolerances() -> None: diff --git a/uv.lock b/uv.lock index 4b05c0e..b759c39 100644 --- a/uv.lock +++ b/uv.lock @@ -55,6 +55,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.5.0" @@ -170,29 +179,124 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/c3/4f2195f512fb172aa425a8803a874b2baa9ba7f80ff7b6080998761fc701/coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00", size = 936952, upload-time = "2026-08-06T13:50:24.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/ac/748cf29eeb2d6be34a3176ce26a4f49e38085ee08e8935f05f6f26ed7e0f/coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f", size = 222608, upload-time = "2026-08-06T13:48:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/0b/02/1abbf5c984677b0aa439cdacaccbf38d248939d8ef8fe1cc7a50d73edb77/coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c", size = 222940, upload-time = "2026-08-06T13:48:28.432Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e1/ff8f9f53d9fcf586125b55d0b1f04ec1c14955fee41e83d5814bee141bb5/coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082", size = 253985, upload-time = "2026-08-06T13:48:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/a1/26/595759762e514e81be1d7d01ed03444303bcd152226a6529998d253f9201/coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac", size = 256492, upload-time = "2026-08-06T13:48:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/b79aabac54d482be23b5fcdd4f4662bff24a78edc4ee29201726929936d5/coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734", size = 257837, upload-time = "2026-08-06T13:48:33.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/0f/bf7f297885a5bf6fd71e5782404e0ff059ca09e8711ceb3a08544abde45a/coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d", size = 260152, upload-time = "2026-08-06T13:48:34.75Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f1/296744e854ff8368542343457414380465e9ceefb9192342feb9d3bc461d/coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf", size = 253978, upload-time = "2026-08-06T13:48:36.434Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/bbdb2e9057493e66220a2e149ca2d301ba0e3a58a83bd6b90de9826d16f3/coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b", size = 255846, upload-time = "2026-08-06T13:48:38.317Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/38015b2b6d21258713bd17e76b59d033b191efb5703589cffd037dfbca20/coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25", size = 253808, upload-time = "2026-08-06T13:48:39.993Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/0d515c1e60ee6fbfd1a0e79c07cd87d388a233b7adc37758735677203808/coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303", size = 258081, upload-time = "2026-08-06T13:48:41.971Z" }, + { url = "https://files.pythonhosted.org/packages/91/71/04d9e7a3642146c6351338aef4ef85ab11dbbb54744c13245caba1aad1c0/coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f", size = 253624, upload-time = "2026-08-06T13:48:43.731Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a7/6c28b74c81ebff66987b0e2522ba5cffa3e90b0c33cb6a2eb264d4ee8cf1/coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5", size = 255280, upload-time = "2026-08-06T13:48:45.58Z" }, + { url = "https://files.pythonhosted.org/packages/52/af/bc19996a7014b98d7bbb0f0939453c67074af65784a3aa16a789a07381fa/coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7", size = 224768, upload-time = "2026-08-06T13:48:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/219484e476d6e101ba0a444852579e05f5b75c37c611a42ed1190f73ef62/coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425", size = 225259, upload-time = "2026-08-06T13:48:49.513Z" }, + { url = "https://files.pythonhosted.org/packages/b7/66/fa77daf4e383e5f776dac62c2409b6af81910ae6fe326bd5170dba74cc63/coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8", size = 224684, upload-time = "2026-08-06T13:48:51.235Z" }, + { url = "https://files.pythonhosted.org/packages/58/5b/f03bf0ce362bbf3f785fa5219620d00778d4ac6fc9e407734828e9c672f6/coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8", size = 223338, upload-time = "2026-08-06T13:48:52.896Z" }, + { url = "https://files.pythonhosted.org/packages/0f/76/e77d0ae22501831cc9f92193e8a957a5caa1dd177f90a6d1d9b106242d92/coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a", size = 223609, upload-time = "2026-08-06T13:48:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/82/1a/b1f089da8d38ac612fa2dd6dc7f4a1a7657d12f3e261d2996edd3a838d0b/coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b", size = 264970, upload-time = "2026-08-06T13:48:56.403Z" }, + { url = "https://files.pythonhosted.org/packages/bf/31/e66d98d6e9c7fcc88470f1e234eaf6b1950dc0dfbf797f7282c1c861da24/coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5", size = 267088, upload-time = "2026-08-06T13:48:58.41Z" }, + { url = "https://files.pythonhosted.org/packages/59/a1/ae94eb2c541add426378408379f233591e069040b1e2cdb33df9498a0682/coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba", size = 269508, upload-time = "2026-08-06T13:49:00.42Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c7/88a10694a1c6a213569766aba9f25847b28155d4ac731b13226db216356d/coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982", size = 270629, upload-time = "2026-08-06T13:49:02.234Z" }, + { url = "https://files.pythonhosted.org/packages/b3/34/d8b8232e5e55169933b59aabcef2fedfa4b9d8897361bb80fcbda146505f/coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c", size = 264043, upload-time = "2026-08-06T13:49:04.102Z" }, + { url = "https://files.pythonhosted.org/packages/7e/35/58b009dbf8c471c7224716478b9fed4a7e1af15320e1ed41660978504663/coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57", size = 266963, upload-time = "2026-08-06T13:49:05.821Z" }, + { url = "https://files.pythonhosted.org/packages/62/aa/57fbda1b42c892968273c56b6ee9dc0f1310850859230a507bc7873b1f65/coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26", size = 264569, upload-time = "2026-08-06T13:49:07.706Z" }, + { url = "https://files.pythonhosted.org/packages/98/8a/360e6e7f24d477b7e889703af0afa878d15b6d4d8d2a822b2835c169a879/coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429", size = 268299, upload-time = "2026-08-06T13:49:09.587Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/6f701261aee21b6b5fa8f7872229406dc917e125069448292223bf213606/coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017", size = 263413, upload-time = "2026-08-06T13:49:11.604Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0f/6f04036edc260ed425af83e834f627fad48941ce97b50bfe6edd8b6fa623/coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839", size = 265725, upload-time = "2026-08-06T13:49:13.38Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ce/d19b5d4d5c49a7bfb925fd74310fee7d28bc99520ac3367ccbc54e662518/coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85", size = 225079, upload-time = "2026-08-06T13:49:15.265Z" }, + { url = "https://files.pythonhosted.org/packages/26/bb/7aa1b3b173faee0679037ca950bbbe1247273656697994d8d13f80f8d4b4/coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e", size = 225911, upload-time = "2026-08-06T13:49:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/4ea9e47426d80038d9222db3c4534cb6021a74b237d3ff97ffd33b6600dd/coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e", size = 225219, upload-time = "2026-08-06T13:49:19.293Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c4/dc5d2ac8f9142e7ec7de66e7bf0591db29d78955a040bd915870d9c0e657/coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9", size = 222604, upload-time = "2026-08-06T13:49:21.279Z" }, + { url = "https://files.pythonhosted.org/packages/70/39/33e63df81fe2ee100897451841c821467635923e58e37c6bd4b46dd8106c/coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a", size = 222944, upload-time = "2026-08-06T13:49:23.187Z" }, + { url = "https://files.pythonhosted.org/packages/99/1f/ef3ffb5557febc75a0d97aa459d0266d7d741110265121cc6d8539343d44/coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54", size = 254050, upload-time = "2026-08-06T13:49:25.008Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f5/1f0f6f77698c3601ca0ae7431e34b24c62ca2f06fecb23b73ed1f651d2be/coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb", size = 256967, upload-time = "2026-08-06T13:49:26.896Z" }, + { url = "https://files.pythonhosted.org/packages/03/7a/2ed9bed79925f4367c83c77f66a89e5ca7229c288d2d19ad5f36d1ca0070/coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed", size = 258587, upload-time = "2026-08-06T13:49:28.692Z" }, + { url = "https://files.pythonhosted.org/packages/45/8c/fa34044f71b7cc4ecb6da9c2408770959b0591fa9b5fb6fb6bca38f94298/coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce", size = 260785, upload-time = "2026-08-06T13:49:30.472Z" }, + { url = "https://files.pythonhosted.org/packages/4f/54/d5727ce36b4524a7394ab9f5f1df378e1f23affcdab01037dc8655185cc7/coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c", size = 254545, upload-time = "2026-08-06T13:49:32.271Z" }, + { url = "https://files.pythonhosted.org/packages/dc/e6/6e3783e576719590194bdffb6dd6d85490801785b7c331e35a245d8cb8b5/coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced", size = 256682, upload-time = "2026-08-06T13:49:34.089Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/bacdbde18b69ed2de424fcf64d9fb0a4913753d4f0eca8bae9daad69f4bd/coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800", size = 254560, upload-time = "2026-08-06T13:49:36.052Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a3/1fb927196e3477c1b48831169ab58ba08f451ba87ae311ff1de68b26a616/coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3", size = 258792, upload-time = "2026-08-06T13:49:38.01Z" }, + { url = "https://files.pythonhosted.org/packages/41/58/30d4c149c69053de0edfe325614c1d28d508f62b1783e0e4a234d2e49136/coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768", size = 253968, upload-time = "2026-08-06T13:49:39.934Z" }, + { url = "https://files.pythonhosted.org/packages/89/e4/77f639371b918aad30dda4051f95404b43578f7f2e2f87ba73e02ed1ff37/coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753", size = 255893, upload-time = "2026-08-06T13:49:41.825Z" }, + { url = "https://files.pythonhosted.org/packages/5c/62/13be29b3ddab35f14c87967a4820a05106d2a3eccb4fa4ff550bf30b75e0/coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2", size = 224768, upload-time = "2026-08-06T13:49:44.08Z" }, + { url = "https://files.pythonhosted.org/packages/a1/70/af0c6be0f964af6954f6b74bc109b0dbca02824696d2520fb17fe1ab06e3/coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809", size = 225242, upload-time = "2026-08-06T13:49:45.899Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2d/f3bd3aab899fc9efc18b53133ee68f5f98574ef480649b23e12962226387/coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d", size = 224674, upload-time = "2026-08-06T13:49:48.322Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ca/f69251cd63eabc6438321aea22148754cce758a26bde07dd490e3fe7cfc5/coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d", size = 223333, upload-time = "2026-08-06T13:49:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a7/037b53b2885b0d8447064432491a4d5a1014cd9f97a594d53acd0c04541a/coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26", size = 223630, upload-time = "2026-08-06T13:49:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/80/4f/152b8a4779ae90da11bb24f7467df8a59f0be48a5c52acb856325ca48289/coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645", size = 264489, upload-time = "2026-08-06T13:49:54.52Z" }, + { url = "https://files.pythonhosted.org/packages/10/2d/84b4b9e0e1dd6528a51920ff7031f35b789382e467a28ec6a5a578cb8812/coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a", size = 267567, upload-time = "2026-08-06T13:49:56.721Z" }, + { url = "https://files.pythonhosted.org/packages/53/fc/ba01cc25299f9f8a2c8b02d3b28c53f3543d9fbfbe4e74fa2760b48f163e/coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624", size = 270123, upload-time = "2026-08-06T13:49:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d0/db2647cbf40b14f8c308f94ff7bf89c06d564e59f396906edf50086ec788/coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa", size = 271107, upload-time = "2026-08-06T13:50:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4d2d17924552c458bb4f77dd631f0e3bc92fbbdf2d2d916cd4b33bbfd5b1/coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f", size = 264955, upload-time = "2026-08-06T13:50:03.023Z" }, + { url = "https://files.pythonhosted.org/packages/ee/de/dc010c7a3691f396d93bbc26bfcafa1c2a3a351cd520470f15faf5795bd5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f", size = 267949, upload-time = "2026-08-06T13:50:05.557Z" }, + { url = "https://files.pythonhosted.org/packages/78/ea/dc96a11375e83c045c2f7c61fb6918277cfe9401db7c0f7b1d111a84b2e5/coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67", size = 264421, upload-time = "2026-08-06T13:50:07.612Z" }, + { url = "https://files.pythonhosted.org/packages/c8/86/b77131a0f9503ce461cd577076147d7a9040f0c5dda772686f729e2cc9cb/coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc", size = 269121, upload-time = "2026-08-06T13:50:09.58Z" }, + { url = "https://files.pythonhosted.org/packages/24/24/944bc35007862955e7ebf05754e645419dcf5d7526c52735cfa2715e8ebf/coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88", size = 264565, upload-time = "2026-08-06T13:50:11.722Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cc/a3bb9f93e7e740659163e2ea584f8196ddcd2c456a5dbe15f6c50105fec1/coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc", size = 266522, upload-time = "2026-08-06T13:50:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/dd/e0e40f3560d878d888c580698ff5ad1179f5e1c3ac949684ef66b41a3817/coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a", size = 225068, upload-time = "2026-08-06T13:50:15.825Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/37732ea80eebc30e976e4cdab15c190bc42d96959a42e38ddf6f8c60468f/coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b", size = 225895, upload-time = "2026-08-06T13:50:17.928Z" }, + { url = "https://files.pythonhosted.org/packages/c6/08/1e00f7923eaaba45fb3d51dd794125fc766304b1df264f3a9c6557bfb30e/coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278", size = 225213, upload-time = "2026-08-06T13:50:19.981Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, +] + [[package]] name = "cwmath" -version = "1.0.0" +version = "1.0.1" source = { editable = "." } [package.dev-dependencies] dev = [ { name = "black" }, { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, + { name = "pre-commit" }, { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, ] [package.metadata] [package.metadata.requires-dev] dev = [ - { name = "black" }, - { name = "mkdocs" }, - { name = "mkdocs-material" }, - { name = "mkdocstrings", extras = ["python"] }, - { name = "pytest" }, + { name = "black", specifier = ">=26.5.1" }, + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-autorefs", specifier = ">=1.4.4" }, + { name = "mkdocs-material", specifier = ">=9.7.7" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.6" }, + { name = "pre-commit", specifier = ">=4.6.2" }, + { name = "pytest", specifier = ">=9.1.1" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "ruff", specifier = ">=0.16.3" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" }, ] [[package]] @@ -216,6 +320,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl", hash = "sha256:cc7b3d2d2865ad0b909fcc38086e3f554b5ea7acbaa7bbb7ecaa3f5dfb7d9f00", size = 142560, upload-time = "2026-06-19T12:05:38.742Z" }, ] +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + [[package]] name = "idna" version = "3.18" @@ -422,6 +535,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "packaging" version = "26.3" @@ -467,6 +589,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -505,6 +643,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -517,6 +669,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/b7/ac44da2cf0e53ada0e419033c2d058219c95dc1403126f163304c9e814b1/python_discovery-1.5.2.tar.gz", hash = "sha256:45fd4f20a4e3f9b7bf2e0817870bc8e3b320a19658da177af800768c82dbf354", size = 82350, upload-time = "2026-08-12T14:05:26.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/45/689603d04b3bb8d7faa00f25c24acef993aab7813b3dbbfc472a459ab0b5/python_discovery-1.5.2-py3-none-any.whl", hash = "sha256:3e338c2d0f15dfaeea57493f4c2c6caebe0e998ea815c30ae8bf8ee21f1112d3", size = 38350, upload-time = "2026-08-12T14:05:25.113Z" }, +] + [[package]] name = "pytokens" version = "0.4.1" @@ -589,6 +753,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] +[[package]] +name = "ruff" +version = "0.16.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, + { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, + { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, + { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, + { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, + { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -607,6 +796,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] +[[package]] +name = "virtualenv" +version = "21.7.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/dc/a6eb1ddfa7f1e390fa599b078453c97edb3f6f846b34fb4eac3e8ea16401/virtualenv-21.7.4.tar.gz", hash = "sha256:c9d960c95fa458171e58222a5ccab7465298e4b6559977865e627c4719f1e825", size = 5345511, upload-time = "2026-08-10T22:54:33.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4c/eb2f52aeeaf30dbd073d315a251a63ae2b8263171ec4428c135140cb0802/virtualenv-21.7.4-py3-none-any.whl", hash = "sha256:376ec93cd6aab3044fa395d7db226db38043b7b5748948044b2a87168525e843", size = 5324444, upload-time = "2026-08-10T22:54:31.515Z" }, +] + [[package]] name = "watchdog" version = "6.0.0"