diff --git a/.github/workflows/code_style.yml b/.github/workflows/code_style.yml new file mode 100644 index 0000000..a471e35 --- /dev/null +++ b/.github/workflows/code_style.yml @@ -0,0 +1,39 @@ +name: Lint and Format + +on: + push: + branches: [ main ] + + pull_request: + branches: [ main ] + paths: [ '**/*.py' ] + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}/${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + python-format: + name: Ruff Lint and Format + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Setup Ruff + uses: astral-sh/ruff-action@v3 + with: + args: --version + + - name: Enforce Lint + run: ruff check . + + - name: Enforce Format + run: | + ruff format --diff . diff --git a/README.md b/README.md index c8b1dc8..8356acf 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,11 @@ evaluations) and a **standard Clenshaw-Curtis** variant (classical rule). import numpy as np import smolpack + def my_func(dim, x): return np.exp(np.sum(x)) + result = smolpack.int_smolyak(my_func, dim=3, qq=5) ``` diff --git a/bin/build.py b/bin/build.py index 919c514..2074c9f 100755 --- a/bin/build.py +++ b/bin/build.py @@ -3,7 +3,7 @@ import os import shlex import shutil -import subprocess +import subprocess # noqa: S404 import sys from pathlib import Path @@ -33,9 +33,11 @@ logger.addHandler(stderr_handler) -def run_command(command, cwd=None): +def run_command(command: str, cwd: str | None = None) -> None: if cwd is None: - logger.warning("No working directory specified. Using current directory.") + logger.warning( + "No working directory specified. Using current directory." + ) cwd = Path.cwd() else: cwd = Path(cwd) @@ -44,7 +46,7 @@ def run_command(command, cwd=None): logger.info(f"Executing command: '{command}' in '{cwd}'") - with subprocess.Popen( + with subprocess.Popen( # noqa: S603 shlex.split(command), cwd=str(cwd), stdout=subprocess.PIPE, @@ -53,7 +55,7 @@ def run_command(command, cwd=None): env=dict(**os.environ, PYTHONUNBUFFERED="1"), text=True, ) as proc: - with open(log_file_path, "a") as _log_file: + with open(log_file_path, "a", encoding="utf-8") as _log_file: for line in proc.stdout: # _log_file.write(line) logger.debug(line.rstrip()) @@ -67,21 +69,27 @@ def run_command(command, cwd=None): logger.info("Command executed successfully.") -def install(): +def install() -> None: run_command("uv pip install . -v") -def wheel(): +def wheel() -> None: run_command("uv build --wheel -v") -def clean(): +def clean() -> None: logger.debug("Starting cleanup ...") run_command("uv pip uninstall smolpack") for entry in Path("").iterdir(): - if entry.name in ["dist", "build", "lib", ".pytest_cache", ".ruff_cache"]: + if entry.name in { + "dist", + "build", + "lib", + ".pytest_cache", + ".ruff_cache", + }: logger.info(f"Removing '{entry}'") shutil.rmtree(entry) if entry.name == "bin" and entry.is_dir(): @@ -102,7 +110,7 @@ def clean(): logger.info("Finished cleanup.") -def main(): +def main() -> None: parser = argparse.ArgumentParser(description="SMOLPACK Build Script") parser.add_argument( "mode", diff --git a/bin/get_version.py b/bin/get_version.py index dd19747..a3934b1 100644 --- a/bin/get_version.py +++ b/bin/get_version.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import subprocess +import subprocess # noqa: S404 import sys from pathlib import Path @@ -16,7 +16,7 @@ from setuptools_scm import get_version -def main(): +def main() -> None: root = Path(__file__).parent.parent version = get_version(root=root) if version is None: diff --git a/docs/api.md b/docs/api.md index c2f6bca..c58d9f3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -39,8 +39,7 @@ approximate integral as a `float`. ### Callback signature ```python -def f(dim: int, x: numpy.ndarray) -> float: - ... +def f(dim: int, x: numpy.ndarray) -> float: ... ``` | Parameter | Type | Description | @@ -78,9 +77,11 @@ combination technique. import numpy as np import smolpack + def f(dim, x): return np.exp(np.sum(x)) + result = smolpack.int_smolyak(f, dim=3, qq=5) ``` @@ -102,9 +103,11 @@ can be more accurate for extremely smooth integrands. import numpy as np import smolpack + def f(dim, x): return np.exp(np.sum(x)) + result = smolpack.cc_int_smolyak(f, dim=3, qq=5) ``` diff --git a/docs/index.md b/docs/index.md index fcfff67..f10dd5a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -51,9 +51,11 @@ import smolpack # Integrate exp(x1 + x2 + x3) over [0,1]^3 # Exact value: (e - 1)^3 ≈ 5.073214 + def my_func(dim, x): return np.exp(np.sum(x)) + result = smolpack.int_smolyak(my_func, dim=3, qq=5) print(f"Integral ≈ {result:.6f}") ``` diff --git a/docs/quickstart.md b/docs/quickstart.md index 0e9dcc6..7326240 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -39,9 +39,11 @@ $$ import numpy as np import smolpack + def exp_sum(dim, x): return np.exp(np.sum(x)) + result = smolpack.int_smolyak(exp_sum, dim=3, qq=5) print(f"Result = {result:.6f}") ``` @@ -69,9 +71,11 @@ Both solvers applied to the same integrand in 3-D: import numpy as np import smolpack + def exp_sum(dim, x): return np.exp(np.sum(x)) + exact = (np.e - 1.0) ** 3 r1 = smolpack.int_smolyak(exp_sum, dim=3, qq=7) @@ -92,14 +96,18 @@ additional function evaluations. import numpy as np import smolpack + def exp_sum(dim, x): return np.exp(np.sum(x)) + exact = (np.e - 1.0) ** 3 for qq in range(4, 10): result = smolpack.int_smolyak(exp_sum, dim=3, qq=qq) - print(f"qq={qq} k={qq-3} result={result:.10f} error={abs(result - exact):.2e}") + print( + f"qq={qq} k={qq - 3} result={result:.10f} error={abs(result - exact):.2e}" + ) ``` ## Example 5: Product integrand (separable) @@ -111,12 +119,14 @@ $(1/2)^d$: import numpy as np import smolpack + def product_x(dim, x): return np.prod(x) + dim = 5 result = smolpack.int_smolyak(product_x, dim=dim, qq=dim + 3) -exact = 0.5 ** dim +exact = 0.5**dim print(f"Result = {result:.10f} (exact = {exact})") ``` @@ -133,9 +143,11 @@ $$ import numpy as np import smolpack + def product_cosine(dim, x): return np.prod(np.cos(x)) + result = smolpack.int_smolyak(product_cosine, dim=10, qq=12) exact = np.sin(1.0) ** 10 print(f"Result = {result:.10e}") @@ -155,10 +167,12 @@ $$ import numpy as np import smolpack + def genz_oscillatory(dim, x): a = np.ones(dim) return np.cos(2.0 * np.pi * a[0] + np.sum(a * x)) + result = smolpack.int_smolyak(genz_oscillatory, dim=5, qq=8) print(f"Oscillatory integral = {result:.8f}") ``` @@ -171,9 +185,11 @@ Set `print_stats=True` to see function-call and weight-evaluation counts: import numpy as np import smolpack + def exp_sum(dim, x): return np.exp(np.sum(x)) + result = smolpack.int_smolyak(exp_sum, dim=3, qq=5, print_stats=True) ``` diff --git a/pyproject.toml b/pyproject.toml index 3e9d895..0d0d274 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ issues = "https://github.com/eggzec/smolpack/issues" dev = [ {include-group = "build"}, {include-group = "docs"}, + {include-group = "lint"}, {include-group = "test"} ] build = [ @@ -65,6 +66,9 @@ build = [ docs = [ "zensical>=0.0.23" ] +lint = [ + "ruff==0.15.*", +] test = [ "pytest>=8.3.5", "pytest-cov>=6.0", @@ -86,3 +90,56 @@ local_scheme = "no-local-version" [tool.cibuildwheel] enable = ["cpython-prerelease"] + +[tool.ruff] +line-length = 80 +indent-width = 4 +preview = true + +# Output serialization format for violations. The default serialization +# format is "full" [env: RUFF_OUTPUT_FORMAT=] [possible values: +# concise, full, json, json-lines, junit, grouped, github, gitlab, +# pylint, rdjson, azure, sarif] +output-format = "grouped" + +[tool.ruff.lint] +isort.lines-after-imports = 2 +isort.split-on-trailing-comma = false + +select = [ + "ANN", # flake8-annotations (required strict type annotations for public functions) + "S", # flake8-bandit (checks basic security issues in code) + "BLE", # flake8-blind-except (checks the except blocks that do not specify exception) + "FBT", # flake8-boolean-trap (ensure that boolean args can be used with kw only) + "E", # pycodestyle errors (PEP 8 style guide violations) + "W", # pycodestyle warnings (e.g., extra spaces, indentation issues) + "DOC", # pydoclint issues (e.g., extra or missing return, yield, warnings) + "A", # flake8-buitins (check variable and function names to not shadow builtins) + "N", # Naming convention checks (e.g., PEP 8 variable and function names) + "F", # Pyflakes errors (e.g., unused imports, undefined variables) + "I", # isort (Ensures imports are sorted properly) + "B", # flake8-bugbear (Detects likely bugs and bad practices) + "TID", # flake8-tidy-imports (Checks for banned or misplaced imports) + "UP", # pyupgrade (Automatically updates old Python syntax) + "YTT", # flake8-2020 (Detects outdated Python 2/3 compatibility issues) + "FLY", # flynt (Converts old-style string formatting to f-strings) + "PIE", # flake8-pie + "PL", # pylint + "RUF", # Ruff-specific rules (Additional optimizations and best practices) +] + +ignore = [] + +[tool.ruff.lint.per-file-ignores] +"**/__init__.py" = ["RUF067"] +"**/tests/*.py" = [ + "ANN001", # Missing type annotation for function argument + "ANN202", # Missing return type annotation for private function + "DOC201", # `return` is not documented in docstring + "PLR6301", # Method could be a function, class method, or static method + "S101", # Use of `assert` detected +] + +[tool.ruff.format] +docstring-code-format = true +skip-magic-trailing-comma = true diff --git a/smolpack/__init__.py b/smolpack/__init__.py index 9a90a87..468527a 100644 --- a/smolpack/__init__.py +++ b/smolpack/__init__.py @@ -15,18 +15,17 @@ >>> >>> def my_func(dim, x): ... return np.exp(np.sum(x)) -... >>> result = smolpack.int_smolyak(my_func, dim=3, qq=5) """ -from ._smolpack import ( - delayed_cc as _delayed_cc, - standard_cc as _standard_cc, - get_count, -) +from ._smolpack import delayed_cc as _delayed_cc +from ._smolpack import get_count +from ._smolpack import standard_cc as _standard_cc -def int_smolyak(f, dim, qq, print_stats=False): +def int_smolyak( + f: callable, dim: int, qq: int, *, print_stats: bool = False +) -> float: """Approximate an integral over [0,1]^dim (delayed Clenshaw-Curtis). Parameters @@ -52,7 +51,9 @@ def int_smolyak(f, dim, qq, print_stats=False): return _delayed_cc(dim, qq, int(print_stats), f) -def cc_int_smolyak(f, dim, qq, print_stats=False): +def cc_int_smolyak( + f: callable, dim: int, qq: int, *, print_stats: bool = False +) -> float: """Approximate an integral over [0,1]^dim (standard Clenshaw-Curtis). Parameters @@ -78,4 +79,4 @@ def cc_int_smolyak(f, dim, qq, print_stats=False): return _standard_cc(dim, qq, int(print_stats), f) -__all__ = ["int_smolyak", "cc_int_smolyak", "get_count"] +__all__ = ["cc_int_smolyak", "get_count", "int_smolyak"] diff --git a/tests/test_cc_int_smolyak.py b/tests/test_cc_int_smolyak.py index 9241277..53d8ed4 100644 --- a/tests/test_cc_int_smolyak.py +++ b/tests/test_cc_int_smolyak.py @@ -9,7 +9,7 @@ def _exp_sum(dim, x): return np.exp(np.sum(x)) -def _constant(dim, x): +def _constant(dim, x) -> float: """Constant function 1. Exact = 1 for any dimension.""" return 1.0 @@ -36,14 +36,14 @@ def _sum_squares(dim, x): class TestCcIntSmolyakConstant: @pytest.mark.parametrize("dim", [1, 2, 5, 10]) - def test_constant(self, dim): + def test_constant(self, dim) -> None: result = smolpack.cc_int_smolyak(_constant, dim=dim, qq=dim + 1) assert result == pytest.approx(1.0, abs=1e-12) class TestCcIntSmolyakLinear: @pytest.mark.parametrize("dim", [1, 2, 3, 5]) - def test_linear(self, dim): + def test_linear(self, dim) -> None: exact = dim / 2.0 result = smolpack.cc_int_smolyak(_sum_linear, dim=dim, qq=dim + 2) assert result == pytest.approx(exact, abs=1e-12) @@ -51,7 +51,7 @@ def test_linear(self, dim): class TestCcIntSmolyakQuadratic: @pytest.mark.parametrize("dim", [1, 2, 3, 5]) - def test_sum_squares(self, dim): + def test_sum_squares(self, dim) -> None: exact = dim / 3.0 result = smolpack.cc_int_smolyak(_sum_squares, dim=dim, qq=dim + 3) assert result == pytest.approx(exact, abs=1e-10) @@ -60,15 +60,9 @@ def test_sum_squares(self, dim): class TestCcIntSmolyakExpSum: @pytest.mark.parametrize( "dim, qq, tol", - [ - (1, 5, 1e-10), - (2, 5, 1e-6), - (3, 5, 1e-4), - (3, 7, 1e-6), - (5, 8, 1e-4), - ], + [(1, 5, 1e-10), (2, 5, 1e-6), (3, 5, 1e-4), (3, 7, 1e-6), (5, 8, 1e-4)], ) - def test_exp_sum(self, dim, qq, tol): + def test_exp_sum(self, dim, qq, tol) -> None: exact = (np.e - 1.0) ** dim result = smolpack.cc_int_smolyak(_exp_sum, dim=dim, qq=qq) assert result == pytest.approx(exact, abs=tol) @@ -76,14 +70,14 @@ def test_exp_sum(self, dim, qq, tol): class TestCcIntSmolyakProduct: @pytest.mark.parametrize("dim", [1, 2, 3, 4]) - def test_product(self, dim): + def test_product(self, dim) -> None: exact = 0.5**dim result = smolpack.cc_int_smolyak(_product_x, dim=dim, qq=dim + 3) assert result == pytest.approx(exact, abs=1e-10) class TestCcIntSmolyakPrintStats: - def test_print_stats_false(self): + def test_print_stats_false(self) -> None: r1 = smolpack.cc_int_smolyak(_exp_sum, dim=2, qq=4, print_stats=False) r2 = smolpack.cc_int_smolyak(_exp_sum, dim=2, qq=4, print_stats=True) assert r1 == pytest.approx(r2, abs=1e-15) diff --git a/tests/test_get_count.py b/tests/test_get_count.py index a4886e5..cc9d216 100644 --- a/tests/test_get_count.py +++ b/tests/test_get_count.py @@ -9,28 +9,28 @@ def _exp_sum(dim, x): return np.exp(np.sum(x)) -def _constant(dim, x): +def _constant(dim, x) -> float: """Constant function 1.""" return 1.0 class TestGetCount: - def test_count_is_int(self): + def test_count_is_int(self) -> None: smolpack.int_smolyak(_exp_sum, dim=2, qq=4) assert isinstance(smolpack.get_count(), int) - def test_count_nonnegative(self): + def test_count_nonnegative(self) -> None: smolpack.int_smolyak(_exp_sum, dim=2, qq=4) assert smolpack.get_count() >= 0 - def test_count_after_cc(self): + def test_count_after_cc(self) -> None: smolpack.cc_int_smolyak(_exp_sum, dim=2, qq=4) assert smolpack.get_count() >= 0 class TestCrossAlgorithm: @pytest.mark.parametrize("dim", [1, 2, 3]) - def test_convergence_agreement(self, dim): + def test_convergence_agreement(self, dim) -> None: qq = dim + 5 r1 = smolpack.int_smolyak(_exp_sum, dim=dim, qq=qq) r2 = smolpack.cc_int_smolyak(_exp_sum, dim=dim, qq=qq) @@ -39,7 +39,7 @@ def test_convergence_agreement(self, dim): assert r1 == pytest.approx(exact, abs=1e-4) assert r2 == pytest.approx(exact, abs=1e-4) - def test_1d_match(self): + def test_1d_match(self) -> None: """In 1-D both algorithms should give very close results.""" r1 = smolpack.int_smolyak(_exp_sum, dim=1, qq=6) r2 = smolpack.cc_int_smolyak(_exp_sum, dim=1, qq=6) @@ -47,10 +47,10 @@ def test_1d_match(self): class TestEdgeCases: - def test_dim_1(self): + def test_dim_1(self) -> None: result = smolpack.int_smolyak(_constant, dim=1, qq=2) assert result == pytest.approx(1.0, abs=1e-14) - def test_high_dim_constant(self): + def test_high_dim_constant(self) -> None: result = smolpack.int_smolyak(_constant, dim=20, qq=21) assert result == pytest.approx(1.0, abs=1e-10) diff --git a/tests/test_int_smolyak.py b/tests/test_int_smolyak.py index d4c6beb..e935c80 100644 --- a/tests/test_int_smolyak.py +++ b/tests/test_int_smolyak.py @@ -9,7 +9,7 @@ def _exp_sum(dim, x): return np.exp(np.sum(x)) -def _constant(dim, x): +def _constant(dim, x) -> float: """Constant function 1. Exact = 1 for any dimension.""" return 1.0 @@ -36,14 +36,14 @@ def _sum_squares(dim, x): class TestIntSmolyakConstant: @pytest.mark.parametrize("dim", [1, 2, 5, 10]) - def test_constant(self, dim): + def test_constant(self, dim) -> None: result = smolpack.int_smolyak(_constant, dim=dim, qq=dim + 1) assert result == pytest.approx(1.0, abs=1e-12) class TestIntSmolyakLinear: @pytest.mark.parametrize("dim", [1, 2, 3, 5]) - def test_linear(self, dim): + def test_linear(self, dim) -> None: exact = dim / 2.0 result = smolpack.int_smolyak(_sum_linear, dim=dim, qq=dim + 2) assert result == pytest.approx(exact, abs=1e-12) @@ -51,7 +51,7 @@ def test_linear(self, dim): class TestIntSmolyakQuadratic: @pytest.mark.parametrize("dim", [1, 2, 3, 5]) - def test_sum_squares(self, dim): + def test_sum_squares(self, dim) -> None: exact = dim / 3.0 result = smolpack.int_smolyak(_sum_squares, dim=dim, qq=dim + 3) assert result == pytest.approx(exact, abs=1e-10) @@ -60,15 +60,9 @@ def test_sum_squares(self, dim): class TestIntSmolyakExpSum: @pytest.mark.parametrize( "dim, qq, tol", - [ - (1, 5, 1e-10), - (2, 5, 1e-6), - (3, 5, 1e-3), - (3, 7, 1e-6), - (5, 8, 1e-3), - ], + [(1, 5, 1e-10), (2, 5, 1e-6), (3, 5, 1e-3), (3, 7, 1e-6), (5, 8, 1e-3)], ) - def test_exp_sum(self, dim, qq, tol): + def test_exp_sum(self, dim, qq, tol) -> None: exact = (np.e - 1.0) ** dim result = smolpack.int_smolyak(_exp_sum, dim=dim, qq=qq) assert result == pytest.approx(exact, abs=tol) @@ -76,14 +70,14 @@ def test_exp_sum(self, dim, qq, tol): class TestIntSmolyakProduct: @pytest.mark.parametrize("dim", [1, 2, 3, 4]) - def test_product(self, dim): + def test_product(self, dim) -> None: exact = 0.5**dim result = smolpack.int_smolyak(_product_x, dim=dim, qq=dim + 3) assert result == pytest.approx(exact, abs=1e-10) class TestIntSmolyakPrintStats: - def test_print_stats_false(self): + def test_print_stats_false(self) -> None: r1 = smolpack.int_smolyak(_exp_sum, dim=2, qq=4, print_stats=False) r2 = smolpack.int_smolyak(_exp_sum, dim=2, qq=4, print_stats=True) assert r1 == pytest.approx(r2, abs=1e-15) diff --git a/uv.lock b/uv.lock index e017d35..90e7c54 100644 --- a/uv.lock +++ b/uv.lock @@ -559,6 +559,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "ruff" +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +] + [[package]] name = "setuptools" version = "82.0.1" @@ -602,12 +627,16 @@ dev = [ { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, + { name = "ruff" }, { name = "setuptools-scm" }, { name = "zensical" }, ] docs = [ { name = "zensical" }, ] +lint = [ + { name = "ruff" }, +] test = [ { name = "pytest" }, { name = "pytest-cov" }, @@ -629,10 +658,12 @@ dev = [ { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" }, + { name = "ruff", specifier = "==0.15.*" }, { name = "setuptools-scm", specifier = ">=8" }, { name = "zensical", specifier = ">=0.0.23" }, ] docs = [{ name = "zensical", specifier = ">=0.0.23" }] +lint = [{ name = "ruff", specifier = "==0.15.*" }] test = [ { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.0" },