From b3da6e03ffadfc3dd385642ba93d3e42ac108444 Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:04:26 +0100 Subject: [PATCH] CI: lint docker tests (#3443) --- .github/workflows/docker-tests.yml | 1 + docker/test/pyproject.toml | 37 ++++++--- docker/test/tests/__init__.py | 0 docker/test/tests/conftest.py | 4 +- docker/test/tests/test_agent.py | 6 +- docker/test/tests/test_agent_only.py | 8 +- docker/test/tests/test_bouncer.py | 10 +-- docker/test/tests/test_capi.py | 6 +- docker/test/tests/test_capi_whitelists.py | 10 +-- docker/test/tests/test_cold_logs.py | 12 ++- docker/test/tests/test_flavors.py | 6 +- docker/test/tests/test_hello.py | 13 ++-- docker/test/tests/test_hub.py | 4 +- docker/test/tests/test_hub_collections.py | 10 +-- docker/test/tests/test_hub_parsers.py | 8 +- docker/test/tests/test_hub_postoverflows.py | 8 +- docker/test/tests/test_hub_scenarios.py | 8 +- docker/test/tests/test_local_api_url.py | 8 +- docker/test/tests/test_local_item.py | 8 +- docker/test/tests/test_metrics.py | 10 +-- docker/test/tests/test_nolapi.py | 4 +- docker/test/tests/test_simple.py | 6 +- docker/test/tests/test_tls.py | 22 +++--- docker/test/tests/test_version.py | 4 +- docker/test/tests/test_wal.py | 8 +- docker/test/uv.lock | 86 ++++++++++++++------- 26 files changed, 155 insertions(+), 152 deletions(-) create mode 100644 docker/test/tests/__init__.py diff --git a/.github/workflows/docker-tests.yml b/.github/workflows/docker-tests.yml index 647f3e55c..796dd916f 100644 --- a/.github/workflows/docker-tests.yml +++ b/.github/workflows/docker-tests.yml @@ -70,6 +70,7 @@ jobs: cd docker/test uv sync --all-extras --dev --locked uv run ruff check + uv run basedpyright uv run pytest tests -n 1 --durations=0 --color=yes env: CROWDSEC_TEST_VERSION: test diff --git a/docker/test/pyproject.toml b/docker/test/pyproject.toml index d32d18442..5ec0c5a7f 100644 --- a/docker/test/pyproject.toml +++ b/docker/test/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ [dependency-groups] dev = [ + "basedpyright>=1.26.0", "ipdb>=0.13.13", "ruff>=0.9.3", ] @@ -26,16 +27,34 @@ line-length = 120 [tool.ruff.lint] select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear - "UP", # pyupgrade - "C90", # macabe + "ALL" ] ignore = [ - "B008", # do not perform function calls in argument defaults + "ANN", # Missing type annotations + "COM", # flake8-commas + "D", # pydocstyle + "ERA", # eradicate + "FIX", # flake8-fixme + "TD", # flake8-todos + + "INP001", # File `...` is part of an implicit namespace package. Add an `__init__.py`. + "E501", # line too long + # ^ does not ignore comments that can't be moved to their own line, line noqa, pyright + # so we take care of line lenghts only with "ruff format" + "PLR2004", # Magic value used in comparison, consider replacing `...` with a constant variable + "S101", # Use of 'assert' detected + "S603", # `subprocess` call: check for execution of untrusted input + "S607", # Starting a process with a partial executable path ] + +[tool.basedpyright] +reportUnknownArgumentType = "none" +reportUnknownParameterType = "none" +reportMissingParameterType = "none" +reportMissingTypeStubs = "none" +reportUnknownVariableType = "none" +reportUnknownMemberType = "none" +reportUnreachable = "none" +reportAny = "none" + diff --git a/docker/test/tests/__init__.py b/docker/test/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docker/test/tests/conftest.py b/docker/test/tests/conftest.py index d32ffa28c..5c9eaded2 100644 --- a/docker/test/tests/conftest.py +++ b/docker/test/tests/conftest.py @@ -1,6 +1,8 @@ +from _pytest.config import Config + pytest_plugins = ("cs",) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.addinivalue_line("markers", "docker: mark tests for lone or manually orchestrated containers") config.addinivalue_line("markers", "compose: mark tests for docker compose projects") diff --git a/docker/test/tests/test_agent.py b/docker/test/tests/test_agent.py index aec1bbdaa..4518c9ea4 100644 --- a/docker/test/tests/test_agent.py +++ b/docker/test/tests/test_agent.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from http import HTTPStatus import pytest @@ -7,7 +5,7 @@ import pytest pytestmark = pytest.mark.docker -def test_no_agent(crowdsec, flavor): +def test_no_agent(crowdsec, flavor: str) -> None: """Test DISABLE_AGENT=true""" env = { "DISABLE_AGENT": "true", @@ -21,7 +19,7 @@ def test_no_agent(crowdsec, flavor): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_machine_register(crowdsec, flavor, tmp_path_factory): +def test_machine_register(crowdsec, flavor: str, tmp_path_factory: pytest.TempPathFactory) -> None: """A local agent is always registered for use by cscli""" data_dir = tmp_path_factory.mktemp("data") diff --git a/docker/test/tests/test_agent_only.py b/docker/test/tests/test_agent_only.py index 4e1689e0b..1da659d7e 100644 --- a/docker/test/tests/test_agent_only.py +++ b/docker/test/tests/test_agent_only.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python - -import random +import secrets from http import HTTPStatus import pytest @@ -8,8 +6,8 @@ import pytest pytestmark = pytest.mark.docker -def test_split_lapi_agent(crowdsec, flavor): - rand = str(random.randint(0, 10000)) +def test_split_lapi_agent(crowdsec, flavor: str) -> None: + rand = str(secrets.randbelow(10000)) lapiname = f"lapi-{rand}" agentname = f"agent-{rand}" diff --git a/docker/test/tests/test_bouncer.py b/docker/test/tests/test_bouncer.py index d87aff734..b186c4579 100644 --- a/docker/test/tests/test_bouncer.py +++ b/docker/test/tests/test_bouncer.py @@ -1,10 +1,7 @@ -#!/usr/bin/env python - """ Test bouncer management: pre-installed, run-time installation and removal. """ -import hashlib import json from http import HTTPStatus @@ -13,12 +10,7 @@ import pytest pytestmark = pytest.mark.docker -def hex512(s): - """Return the sha512 hash of a string as a hex string""" - return hashlib.sha512(s.encode()).hexdigest() - - -def test_register_bouncer_env(crowdsec, flavor): +def test_register_bouncer_env(crowdsec, flavor: str) -> None: """Test installing bouncers at startup, from envvar""" env = {"BOUNCER_KEY_bouncer1name": "bouncer1key", "BOUNCER_KEY_bouncer2name": "bouncer2key"} diff --git a/docker/test/tests/test_capi.py b/docker/test/tests/test_capi.py index ad25f7a76..bc748b02e 100644 --- a/docker/test/tests/test_capi.py +++ b/docker/test/tests/test_capi.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from http import HTTPStatus import pytest @@ -7,7 +5,7 @@ import pytest pytestmark = pytest.mark.docker -def test_no_capi(crowdsec, flavor): +def test_no_capi(crowdsec, flavor: str) -> None: """Test no CAPI (disabled by default in tests)""" env = { @@ -26,7 +24,7 @@ def test_no_capi(crowdsec, flavor): assert not any("Registration to online API done" in line for line in logs) -def test_capi(crowdsec, flavor): +def test_capi(crowdsec, flavor: str) -> None: """Test CAPI""" env = { diff --git a/docker/test/tests/test_capi_whitelists.py b/docker/test/tests/test_capi_whitelists.py index 6cdd5f401..8ed0c531e 100644 --- a/docker/test/tests/test_capi_whitelists.py +++ b/docker/test/tests/test_capi_whitelists.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from http import HTTPStatus import pytest @@ -8,16 +6,12 @@ import yaml pytestmark = pytest.mark.docker -def test_capi_whitelists( - crowdsec, - tmp_path_factory, - flavor, -): +def test_capi_whitelists(crowdsec, tmp_path_factory: pytest.TempPathFactory, flavor: str) -> None: """Test CAPI_WHITELISTS_PATH""" env = {"CAPI_WHITELISTS_PATH": "/path/to/whitelists.yaml"} whitelists = tmp_path_factory.mktemp("whitelists") - with open(whitelists / "whitelists.yaml", "w") as f: + with (whitelists / "whitelists.yaml").open("w") as f: yaml.dump({"ips": ["1.2.3.4", "2.3.4.5"], "cidrs": ["1.2.3.0/24"]}, f) volumes = {whitelists / "whitelists.yaml": {"bind": "/path/to/whitelists.yaml", "mode": "ro"}} diff --git a/docker/test/tests/test_cold_logs.py b/docker/test/tests/test_cold_logs.py index 2eb3248ff..11c332cb3 100644 --- a/docker/test/tests/test_cold_logs.py +++ b/docker/test/tests/test_cold_logs.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import datetime import pytest @@ -8,19 +6,19 @@ from pytest_cs import Status pytestmark = pytest.mark.docker -def test_cold_logs(crowdsec, tmp_path_factory, flavor): +def test_cold_logs(crowdsec, tmp_path_factory: pytest.TempPathFactory, flavor: str) -> None: env = { "DSN": "file:///var/log/toto.log", } logs = tmp_path_factory.mktemp("logs") - now = datetime.datetime.now() - datetime.timedelta(minutes=1) - with open(logs / "toto.log", "w") as f: + now = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(minutes=1) + with (logs / "toto.log").open("w") as f: # like date '+%b %d %H:%M:%S' but in python for i in range(10): ts = (now + datetime.timedelta(seconds=i)).strftime("%b %d %H:%M:%S") - f.write(ts + " sd-126005 sshd[12422]: Invalid user netflix from 1.1.1.172 port 35424\n") + _ = f.write(ts + " sd-126005 sshd[12422]: Invalid user netflix from 1.1.1.172 port 35424\n") volumes = { logs / "toto.log": {"bind": "/var/log/toto.log", "mode": "ro"}, @@ -44,7 +42,7 @@ def test_cold_logs(crowdsec, tmp_path_factory, flavor): ) -def test_cold_logs_missing_dsn(crowdsec, flavor): +def test_cold_logs_missing_dsn(crowdsec, flavor: str) -> None: env = { "TYPE": "syslog", } diff --git a/docker/test/tests/test_flavors.py b/docker/test/tests/test_flavors.py index a48fe428c..682213f0c 100644 --- a/docker/test/tests/test_flavors.py +++ b/docker/test/tests/test_flavors.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test basic behavior of all the image variants """ @@ -11,7 +9,7 @@ import pytest pytestmark = pytest.mark.docker -def test_cscli_lapi(crowdsec, flavor): +def test_cscli_lapi(crowdsec, flavor: str) -> None: """Test if cscli can talk to lapi""" with crowdsec(flavor=flavor) as cs: cs.wait_for_log("*Starting processing data*") @@ -23,7 +21,7 @@ def test_cscli_lapi(crowdsec, flavor): @pytest.mark.skip(reason="currently broken by hub upgrade") -def test_flavor_content(crowdsec, flavor): +def test_flavor_content(crowdsec, flavor: str) -> None: """Test flavor contents""" with crowdsec(flavor=flavor) as cs: cs.wait_for_log("*Starting processing data*") diff --git a/docker/test/tests/test_hello.py b/docker/test/tests/test_hello.py index a3ff4f07a..bf6f5cb2e 100644 --- a/docker/test/tests/test_hello.py +++ b/docker/test/tests/test_hello.py @@ -1,31 +1,30 @@ -#!/usr/bin/env python - """ Smoke tests in case docker is not set up correctly or has connection issues. """ import subprocess +import docker import pytest pytestmark = pytest.mark.docker -def test_docker_cli_run(): +def test_docker_cli_run() -> None: """Test if docker run works from the command line. Capture stdout too""" - res = subprocess.run(["docker", "run", "--rm", "hello-world"], capture_output=True, text=True) - assert 0 == res.returncode + res = subprocess.run(["docker", "run", "--rm", "hello-world"], capture_output=True, text=True, check=True) + assert res.returncode == 0 assert "Hello from Docker!" in res.stdout -def test_docker_run(docker_client): +def test_docker_run(docker_client: docker.DockerClient) -> None: """Test if docker run works from the python SDK.""" output = docker_client.containers.run("hello-world", remove=True) lines = output.decode().splitlines() assert "Hello from Docker!" in lines -def test_docker_run_detach(docker_client): +def test_docker_run_detach(docker_client: docker.DockerClient) -> None: """Test with python SDK (async).""" cont = docker_client.containers.run("hello-world", detach=True) assert cont.status == "created" diff --git a/docker/test/tests/test_hub.py b/docker/test/tests/test_hub.py index a7134fcb5..e64ab4a41 100644 --- a/docker/test/tests/test_hub.py +++ b/docker/test/tests/test_hub.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test pre-installed hub items. """ @@ -12,7 +10,7 @@ import pytest pytestmark = pytest.mark.docker -def test_preinstalled_hub(crowdsec, flavor): +def test_preinstalled_hub(crowdsec, flavor: str) -> None: """Test hub objects installed in the entrypoint""" with crowdsec(flavor=flavor) as cs: cs.wait_for_log("*Starting processing data*") diff --git a/docker/test/tests/test_hub_collections.py b/docker/test/tests/test_hub_collections.py index 71fa698af..929d77361 100644 --- a/docker/test/tests/test_hub_collections.py +++ b/docker/test/tests/test_hub_collections.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test collection management """ @@ -12,7 +10,7 @@ import pytest pytestmark = pytest.mark.docker -def test_install_two_collections(crowdsec, flavor): +def test_install_two_collections(crowdsec, flavor: str) -> None: """Test installing collections at startup""" it1 = "crowdsecurity/apache2" it2 = "crowdsecurity/asterisk" @@ -33,7 +31,7 @@ def test_install_two_collections(crowdsec, flavor): ) -def test_disable_collection(crowdsec, flavor): +def test_disable_collection(crowdsec, flavor: str) -> None: """Test removing a pre-installed collection at startup""" it = "crowdsecurity/linux" env = {"DISABLE_COLLECTIONS": it} @@ -52,7 +50,7 @@ def test_disable_collection(crowdsec, flavor): ) -def test_install_and_disable_collection(crowdsec, flavor): +def test_install_and_disable_collection(crowdsec, flavor: str) -> None: """Declare a collection to install AND disable: disable wins""" it = "crowdsecurity/apache2" env = { @@ -73,7 +71,7 @@ def test_install_and_disable_collection(crowdsec, flavor): # already done in bats, prividing here as example of a somewhat complex test -def test_taint_bubble_up(crowdsec, tmp_path_factory, flavor): +def test_taint_bubble_up(crowdsec, flavor: str) -> None: coll = "crowdsecurity/nginx" env = {"COLLECTIONS": f"{coll}"} diff --git a/docker/test/tests/test_hub_parsers.py b/docker/test/tests/test_hub_parsers.py index 42794d20b..33414b54a 100644 --- a/docker/test/tests/test_hub_parsers.py +++ b/docker/test/tests/test_hub_parsers.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test parser management """ @@ -12,7 +10,7 @@ import pytest pytestmark = pytest.mark.docker -def test_install_two_parsers(crowdsec, flavor): +def test_install_two_parsers(crowdsec, flavor: str) -> None: """Test installing parsers at startup""" it1 = "crowdsecurity/cpanel-logs" it2 = "crowdsecurity/cowrie-logs" @@ -29,7 +27,7 @@ def test_install_two_parsers(crowdsec, flavor): # XXX check that the parser is preinstalled by default -def test_disable_parser(crowdsec, flavor): +def test_disable_parser(crowdsec, flavor: str) -> None: """Test removing a pre-installed parser at startup""" it = "crowdsecurity/whitelists" env = {"DISABLE_PARSERS": it} @@ -48,7 +46,7 @@ def test_disable_parser(crowdsec, flavor): assert it not in items -def test_install_and_disable_parser(crowdsec, flavor): +def test_install_and_disable_parser(crowdsec, flavor: str) -> None: """Declare a parser to install AND disable: disable wins""" it = "crowdsecurity/cpanel-logs" env = { diff --git a/docker/test/tests/test_hub_postoverflows.py b/docker/test/tests/test_hub_postoverflows.py index 69f383cda..b082102d5 100644 --- a/docker/test/tests/test_hub_postoverflows.py +++ b/docker/test/tests/test_hub_postoverflows.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test postoverflow management """ @@ -12,7 +10,7 @@ import pytest pytestmark = pytest.mark.docker -def test_install_two_postoverflows(crowdsec, flavor): +def test_install_two_postoverflows(crowdsec, flavor: str) -> None: """Test installing postoverflows at startup""" it1 = "crowdsecurity/cdn-whitelist" it2 = "crowdsecurity/ipv6_to_range" @@ -30,12 +28,12 @@ def test_install_two_postoverflows(crowdsec, flavor): assert items[it2]["status"] == "enabled" -def test_disable_postoverflow(): +def test_disable_postoverflow() -> None: """Test removing a pre-installed postoverflow at startup""" pytest.skip("we don't preinstall postoverflows") -def test_install_and_disable_postoverflow(crowdsec, flavor): +def test_install_and_disable_postoverflow(crowdsec, flavor: str) -> None: """Declare a postoverflow to install AND disable: disable wins""" it = "crowdsecurity/cdn-whitelist" env = { diff --git a/docker/test/tests/test_hub_scenarios.py b/docker/test/tests/test_hub_scenarios.py index 4376a3ce6..207461b48 100644 --- a/docker/test/tests/test_hub_scenarios.py +++ b/docker/test/tests/test_hub_scenarios.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test scenario management """ @@ -12,7 +10,7 @@ import pytest pytestmark = pytest.mark.docker -def test_install_two_scenarios(crowdsec, flavor): +def test_install_two_scenarios(crowdsec, flavor: str) -> None: """Test installing scenarios at startup""" it1 = "crowdsecurity/cpanel-bf-attempt" it2 = "crowdsecurity/asterisk_bf" @@ -28,7 +26,7 @@ def test_install_two_scenarios(crowdsec, flavor): assert items[it2]["status"] == "enabled" -def test_disable_scenario(crowdsec, flavor): +def test_disable_scenario(crowdsec, flavor: str) -> None: """Test removing a pre-installed scenario at startup""" it = "crowdsecurity/ssh-bf" env = {"DISABLE_SCENARIOS": it} @@ -42,7 +40,7 @@ def test_disable_scenario(crowdsec, flavor): assert it not in items -def test_install_and_disable_scenario(crowdsec, flavor): +def test_install_and_disable_scenario(crowdsec, flavor: str) -> None: """Declare a scenario to install AND disable: disable wins""" it = "crowdsecurity/asterisk_bf" env = { diff --git a/docker/test/tests/test_local_api_url.py b/docker/test/tests/test_local_api_url.py index e38af3fed..72cf120e4 100644 --- a/docker/test/tests/test_local_api_url.py +++ b/docker/test/tests/test_local_api_url.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from http import HTTPStatus import pytest @@ -7,7 +5,7 @@ import pytest pytestmark = pytest.mark.docker -def test_local_api_url_default(crowdsec, flavor): +def test_local_api_url_default(crowdsec, flavor: str) -> None: """Test LOCAL_API_URL (default)""" with crowdsec(flavor=flavor) as cs: cs.wait_for_log(["*CrowdSec Local API listening on *:8080*", "*Starting processing data*"]) @@ -19,7 +17,7 @@ def test_local_api_url_default(crowdsec, flavor): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_local_api_url(crowdsec, flavor): +def test_local_api_url(crowdsec, flavor: str) -> None: """Test LOCAL_API_URL (custom)""" env = {"LOCAL_API_URL": "http://127.0.0.1:8080"} with crowdsec(flavor=flavor, environment=env) as cs: @@ -32,7 +30,7 @@ def test_local_api_url(crowdsec, flavor): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_local_api_url_ipv6(crowdsec, flavor): +def test_local_api_url_ipv6(crowdsec, flavor: str) -> None: """Test LOCAL_API_URL (custom with ipv6)""" pytest.skip("ipv6 not supported yet") diff --git a/docker/test/tests/test_local_item.py b/docker/test/tests/test_local_item.py index e4c8e3c16..4309ad578 100644 --- a/docker/test/tests/test_local_item.py +++ b/docker/test/tests/test_local_item.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - """ Test bind-mounting local items """ @@ -12,14 +10,14 @@ import pytest pytestmark = pytest.mark.docker -def test_inject_local_item(crowdsec, tmp_path_factory, flavor): +def test_inject_local_item(crowdsec, tmp_path_factory: pytest.TempPathFactory, flavor: str) -> None: """Test mounting a custom whitelist at startup""" localitems = tmp_path_factory.mktemp("localitems") custom_whitelists = localitems / "custom_whitelists.yaml" - with open(custom_whitelists, "w") as f: - f.write('{"whitelist":{"reason":"Good IPs","ip":["1.2.3.4"]}}') + with custom_whitelists.open("w") as f: + _ = f.write('{"whitelist":{"reason":"Good IPs","ip":["1.2.3.4"]}}') volumes = {custom_whitelists: {"bind": "/etc/crowdsec/parsers/s02-enrich/custom_whitelists.yaml"}} diff --git a/docker/test/tests/test_metrics.py b/docker/test/tests/test_metrics.py index bd41bdcea..34261349c 100644 --- a/docker/test/tests/test_metrics.py +++ b/docker/test/tests/test_metrics.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from http import HTTPStatus import pytest @@ -7,7 +5,7 @@ import pytest pytestmark = pytest.mark.docker -def test_metrics_port_default(crowdsec, flavor): +def test_metrics_port_default(crowdsec, flavor: str) -> None: """Test metrics""" metrics_port = 6060 with crowdsec(flavor=flavor) as cs: @@ -23,7 +21,7 @@ def test_metrics_port_default(crowdsec, flavor): assert "# HELP cs_info Information about Crowdsec." in stdout -def test_metrics_port_default_ipv6(crowdsec, flavor): +def test_metrics_port_default_ipv6(crowdsec, flavor: str) -> None: """Test metrics (ipv6)""" pytest.skip("ipv6 not supported yet") port = 6060 @@ -39,7 +37,7 @@ def test_metrics_port_default_ipv6(crowdsec, flavor): assert "# HELP cs_info Information about Crowdsec." in stdout -def test_metrics_port(crowdsec, flavor): +def test_metrics_port(crowdsec, flavor: str) -> None: """Test metrics (custom METRICS_PORT)""" port = 7070 env = {"METRICS_PORT": port} @@ -55,7 +53,7 @@ def test_metrics_port(crowdsec, flavor): assert "# HELP cs_info Information about Crowdsec." in stdout -def test_metrics_port_ipv6(crowdsec, flavor): +def test_metrics_port_ipv6(crowdsec, flavor: str) -> None: """Test metrics (custom METRICS_PORT, ipv6)""" pytest.skip("ipv6 not supported yet") port = 7070 diff --git a/docker/test/tests/test_nolapi.py b/docker/test/tests/test_nolapi.py index e5dbc3c26..da8849139 100644 --- a/docker/test/tests/test_nolapi.py +++ b/docker/test/tests/test_nolapi.py @@ -1,12 +1,10 @@ -#!/usr/bin/env python - import pytest from pytest_cs import Status pytestmark = pytest.mark.docker -def test_no_agent(crowdsec, flavor): +def test_no_agent(crowdsec, flavor: str) -> None: """Test DISABLE_LOCAL_API=true (failing stand-alone container)""" env = { "DISABLE_LOCAL_API": "true", diff --git a/docker/test/tests/test_simple.py b/docker/test/tests/test_simple.py index b5c8425b3..1ae7e5c70 100644 --- a/docker/test/tests/test_simple.py +++ b/docker/test/tests/test_simple.py @@ -1,16 +1,14 @@ -#!/usr/bin/env python - import pytest pytestmark = pytest.mark.docker # XXX this is redundant, already tested in pytest_cs -def test_crowdsec(crowdsec, flavor): +def test_crowdsec(crowdsec, flavor: str) -> None: with crowdsec(flavor=flavor) as cs: for waiter in cs.log_waiters(): with waiter as matcher: matcher.fnmatch_lines(["*Starting processing data*"]) res = cs.cont.exec_run('sh -c "echo $CI_TESTING"') assert res.exit_code == 0 - assert "true" == res.output.decode().strip() + assert res.output.decode().strip() == "true" diff --git a/docker/test/tests/test_tls.py b/docker/test/tests/test_tls.py index 220738a9f..15f5c4663 100644 --- a/docker/test/tests/test_tls.py +++ b/docker/test/tests/test_tls.py @@ -1,10 +1,10 @@ -#!/usr/bin/env python - """ Test agent-lapi and cscli-lapi communication via TLS, on the same container. """ +import pathlib import uuid +from collections.abc import Callable import pytest from pytest_cs import Status @@ -12,7 +12,7 @@ from pytest_cs import Status pytestmark = pytest.mark.docker -def test_missing_key_file(crowdsec, flavor): +def test_missing_key_file(crowdsec, flavor: str) -> None: """Test that cscli and agent can communicate to LAPI with TLS""" env = { @@ -24,7 +24,7 @@ def test_missing_key_file(crowdsec, flavor): cs.wait_for_log("*local API server stopped with error: missing TLS key file*") -def test_missing_cert_file(crowdsec, flavor): +def test_missing_cert_file(crowdsec, flavor: str) -> None: """Test that cscli and agent can communicate to LAPI with TLS""" env = { @@ -36,7 +36,7 @@ def test_missing_cert_file(crowdsec, flavor): cs.wait_for_log("*local API server stopped with error: missing TLS cert file*") -def test_tls_missing_ca(crowdsec, flavor, certs_dir): +def test_tls_missing_ca(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Missing CA cert, unknown authority""" env = { @@ -54,7 +54,7 @@ def test_tls_missing_ca(crowdsec, flavor, certs_dir): cs.wait_for_log("*certificate signed by unknown authority*") -def test_tls_legacy_var(crowdsec, flavor, certs_dir): +def test_tls_legacy_var(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Test server-only certificate, legacy variables""" env = { @@ -79,7 +79,7 @@ def test_tls_legacy_var(crowdsec, flavor, certs_dir): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_tls_mutual_monolith(crowdsec, flavor, certs_dir): +def test_tls_mutual_monolith(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Server and client certificates, on the same container""" env = { @@ -106,7 +106,7 @@ def test_tls_mutual_monolith(crowdsec, flavor, certs_dir): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_tls_lapi_var(crowdsec, flavor, certs_dir): +def test_tls_lapi_var(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Test server-only certificate, lapi variables""" env = { @@ -136,7 +136,7 @@ def test_tls_lapi_var(crowdsec, flavor, certs_dir): # we must set insecure_skip_verify to true to use it -def test_tls_split_lapi_agent(crowdsec, flavor, certs_dir): +def test_tls_split_lapi_agent(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Server-only certificate, split containers""" rand = uuid.uuid1() @@ -188,7 +188,7 @@ def test_tls_split_lapi_agent(crowdsec, flavor, certs_dir): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_tls_mutual_split_lapi_agent(crowdsec, flavor, certs_dir): +def test_tls_mutual_split_lapi_agent(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Server and client certificates, split containers""" rand = uuid.uuid1() @@ -238,7 +238,7 @@ def test_tls_mutual_split_lapi_agent(crowdsec, flavor, certs_dir): assert "You can successfully interact with Local API (LAPI)" in stdout -def test_tls_client_ou(crowdsec, flavor, certs_dir): +def test_tls_client_ou(crowdsec, flavor: str, certs_dir: Callable[..., pathlib.Path]) -> None: """Check behavior of client certificate vs AGENTS_ALLOWED_OU""" rand = uuid.uuid1() diff --git a/docker/test/tests/test_version.py b/docker/test/tests/test_version.py index baac61c36..8e01c639f 100644 --- a/docker/test/tests/test_version.py +++ b/docker/test/tests/test_version.py @@ -1,11 +1,9 @@ -#!/usr/bin/env python - import pytest pytestmark = pytest.mark.docker -def test_version_docker_platform(crowdsec, flavor): +def test_version_docker_platform(crowdsec, flavor: str) -> None: with crowdsec(flavor=flavor) as cs: for waiter in cs.log_waiters(): with waiter as matcher: diff --git a/docker/test/tests/test_wal.py b/docker/test/tests/test_wal.py index e1fe3d260..1431866c6 100644 --- a/docker/test/tests/test_wal.py +++ b/docker/test/tests/test_wal.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - from http import HTTPStatus import pytest @@ -7,7 +5,7 @@ import pytest pytestmark = pytest.mark.docker -def test_use_wal_default(crowdsec, flavor): +def test_use_wal_default(crowdsec, flavor: str) -> None: """Test USE_WAL default""" with crowdsec(flavor=flavor) as cs: cs.wait_for_log("*Starting processing data*") @@ -18,7 +16,7 @@ def test_use_wal_default(crowdsec, flavor): assert "false" in stdout -def test_use_wal_true(crowdsec, flavor): +def test_use_wal_true(crowdsec, flavor: str) -> None: """Test USE_WAL=true""" env = { "USE_WAL": "true", @@ -32,7 +30,7 @@ def test_use_wal_true(crowdsec, flavor): assert "true" in stdout -def test_use_wal_false(crowdsec, flavor): +def test_use_wal_false(crowdsec, flavor: str) -> None: """Test USE_WAL=false""" env = { "USE_WAL": "false", diff --git a/docker/test/uv.lock b/docker/test/uv.lock index d8cc42c89..a930db9cd 100644 --- a/docker/test/uv.lock +++ b/docker/test/uv.lock @@ -11,12 +11,24 @@ wheels = [ ] [[package]] -name = "certifi" -version = "2024.12.14" +name = "basedpyright" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } +dependencies = [ + { name = "nodejs-wheel-binaries" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/c2/5685d040d4f2598788d42bfd2db5f808e9aa2eaee77fcae3c2fbe4ea0e7c/basedpyright-1.26.0.tar.gz", hash = "sha256:5e01f6eb9290a09ef39672106cf1a02924fdc8970e521838bc502ccf0676f32f", size = 24932771 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, + { url = "https://files.pythonhosted.org/packages/8e/72/65308f45bb73efc93075426cac5f37eea937ae364aa675785521cb3512c7/basedpyright-1.26.0-py3-none-any.whl", hash = "sha256:5a6a17f2c389ec313dd2c3644f40e8221bc90252164802e626055341c0a37381", size = 11504579 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, ] [[package]] @@ -109,6 +121,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "basedpyright" }, { name = "ipdb" }, { name = "ruff" }, ] @@ -123,6 +136,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "basedpyright", specifier = ">=1.26.0" }, { name = "ipdb", specifier = ">=0.13.13" }, { name = "ruff", specifier = ">=0.9.3" }, ] @@ -232,7 +246,7 @@ wheels = [ [[package]] name = "ipython" -version = "8.31.0" +version = "8.32.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -245,9 +259,9 @@ dependencies = [ { name = "stack-data" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/35/6f90fdddff7a08b7b715fccbd2427b5212c9525cd043d26fdc45bee0708d/ipython-8.31.0.tar.gz", hash = "sha256:b6a2274606bec6166405ff05e54932ed6e5cfecaca1fc05f2cacde7bb074d70b", size = 5501011 } +sdist = { url = "https://files.pythonhosted.org/packages/36/80/4d2a072e0db7d250f134bc11676517299264ebe16d62a8619d49a78ced73/ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251", size = 5507441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/60/d0feb6b6d9fe4ab89fe8fe5b47cbf6cd936bfd9f1e7ffa9d0015425aeed6/ipython-8.31.0-py3-none-any.whl", hash = "sha256:46ec58f8d3d076a61d128fe517a51eb730e3aaf0c184ea8c17d16e366660c6a6", size = 821583 }, + { url = "https://files.pythonhosted.org/packages/e7/e1/f4474a7ecdb7745a820f6f6039dc43c66add40f1bcc66485607d93571af6/ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa", size = 825524 }, ] [[package]] @@ -274,6 +288,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, ] +[[package]] +name = "nodejs-wheel-binaries" +version = "22.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/c5/1af2fc54fcc18f4a99426b46f18832a04f755ee340019e1be536187c1e1c/nodejs_wheel_binaries-22.13.1.tar.gz", hash = "sha256:a0c15213c9c3383541be4400a30959883868ce5da9cebb3d63ddc7fe61459308", size = 8053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e9/b0dd118e0fd4eabe1ec9c3d9a68df4d811282e8837b811d804f23742e117/nodejs_wheel_binaries-22.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:e4f64d0e26600d51cbdd98a6718a19c2d1b8c7538e9e353e95a634a06a8e1a58", size = 51015650 }, + { url = "https://files.pythonhosted.org/packages/cc/a6/9ba835f5d4f3f6b1f01191e7ac0874871f9743de5c42a5a9a54e67c2e2a6/nodejs_wheel_binaries-22.13.1-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:afcb40484bb02f23137f838014724604ae183fd767b30da95b0be1510a40c06d", size = 51814957 }, + { url = "https://files.pythonhosted.org/packages/0d/2e/a430207e5f22bd3dcffb81acbddf57ee4108b9e2b0f99a5578dc2c1ff7fc/nodejs_wheel_binaries-22.13.1-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fc88c98eebabfc36b5270a4ab974a2682746931567ca76a5ca49c54482bbb51", size = 57148437 }, + { url = "https://files.pythonhosted.org/packages/97/f4/5731b6f0c8af434619b4f1b8fd895bc33fca60168cd68133e52841872114/nodejs_wheel_binaries-22.13.1-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9f75ea8f5e3e5416256fcb00a98cbe14c8d3b6dcaf17da29c4ade5723026d8", size = 57634451 }, + { url = "https://files.pythonhosted.org/packages/49/28/83166f7e39812e9ef99cfa3e722c54e32dd9de6a1290f3216c2e5d1f4957/nodejs_wheel_binaries-22.13.1-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:94608702ef6c389d32e89ff3b7a925cb5dedaf55b5d98bd0c4fb3450a8b6d1c1", size = 58794510 }, + { url = "https://files.pythonhosted.org/packages/f7/64/4832ec26d0a7ca7a5574df265d85c6832f9a624024511fc34958227ad740/nodejs_wheel_binaries-22.13.1-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53a40d0269689aa2eaf2e261cbe5ec256644bc56aae0201ef344b7d8f40ccc79", size = 59738596 }, + { url = "https://files.pythonhosted.org/packages/18/cd/def29615dac250cda3d141e1c03b7153b9a027360bde0272a6768c5fae33/nodejs_wheel_binaries-22.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:549371a929a29fbce8d0ab8f1b5410549946d4f1b0376a5ce635b45f6d05298f", size = 40455444 }, + { url = "https://files.pythonhosted.org/packages/15/d7/6de2bc615203bf590ca437a5cac145b2f86d994ce329489125a0a90ba715/nodejs_wheel_binaries-22.13.1-py2.py3-none-win_arm64.whl", hash = "sha256:cf72d50d755f4e5c0709b0449de01768d96b3b1ec7aa531561415b88f179ad8b", size = 36200929 }, +] + [[package]] name = "packaging" version = "24.2" @@ -393,8 +423,8 @@ wheels = [ [[package]] name = "pytest-cs" -version = "0.7.20" -source = { git = "https://github.com/crowdsecurity/pytest-cs#73380b837a80337f361414bebbaf4b914713c4ae" } +version = "0.7.21" +source = { git = "https://github.com/crowdsecurity/pytest-cs#1eb949d7befa6fe172bf459616b267d4ffc01179" } dependencies = [ { name = "docker" }, { name = "psutil" }, @@ -509,27 +539,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.3" +version = "0.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/7f/60fda2eec81f23f8aa7cbbfdf6ec2ca11eb11c273827933fb2541c2ce9d8/ruff-0.9.3.tar.gz", hash = "sha256:8293f89985a090ebc3ed1064df31f3b4b56320cdfcec8b60d3295bddb955c22a", size = 3586740 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/17/529e78f49fc6f8076f50d985edd9a2cf011d1dbadb1cdeacc1d12afc1d26/ruff-0.9.4.tar.gz", hash = "sha256:6907ee3529244bb0ed066683e075f09285b38dd5b4039370df6ff06041ca19e7", size = 3599458 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/77/4fb790596d5d52c87fd55b7160c557c400e90f6116a56d82d76e95d9374a/ruff-0.9.3-py3-none-linux_armv6l.whl", hash = "sha256:7f39b879064c7d9670197d91124a75d118d00b0990586549949aae80cdc16624", size = 11656815 }, - { url = "https://files.pythonhosted.org/packages/a2/a8/3338ecb97573eafe74505f28431df3842c1933c5f8eae615427c1de32858/ruff-0.9.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a187171e7c09efa4b4cc30ee5d0d55a8d6c5311b3e1b74ac5cb96cc89bafc43c", size = 11594821 }, - { url = "https://files.pythonhosted.org/packages/8e/89/320223c3421962762531a6b2dd58579b858ca9916fb2674874df5e97d628/ruff-0.9.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c59ab92f8e92d6725b7ded9d4a31be3ef42688a115c6d3da9457a5bda140e2b4", size = 11040475 }, - { url = "https://files.pythonhosted.org/packages/b2/bd/1d775eac5e51409535804a3a888a9623e87a8f4b53e2491580858a083692/ruff-0.9.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc153c25e715be41bb228bc651c1e9b1a88d5c6e5ed0194fa0dfea02b026439", size = 11856207 }, - { url = "https://files.pythonhosted.org/packages/7f/c6/3e14e09be29587393d188454064a4aa85174910d16644051a80444e4fd88/ruff-0.9.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:646909a1e25e0dc28fbc529eab8eb7bb583079628e8cbe738192853dbbe43af5", size = 11420460 }, - { url = "https://files.pythonhosted.org/packages/ef/42/b7ca38ffd568ae9b128a2fa76353e9a9a3c80ef19746408d4ce99217ecc1/ruff-0.9.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a5a46e09355695fbdbb30ed9889d6cf1c61b77b700a9fafc21b41f097bfbba4", size = 12605472 }, - { url = "https://files.pythonhosted.org/packages/a6/a1/3167023f23e3530fde899497ccfe239e4523854cb874458ac082992d206c/ruff-0.9.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c4bb09d2bbb394e3730d0918c00276e79b2de70ec2a5231cd4ebb51a57df9ba1", size = 13243123 }, - { url = "https://files.pythonhosted.org/packages/d0/b4/3c600758e320f5bf7de16858502e849f4216cb0151f819fa0d1154874802/ruff-0.9.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96a87ec31dc1044d8c2da2ebbed1c456d9b561e7d087734336518181b26b3aa5", size = 12744650 }, - { url = "https://files.pythonhosted.org/packages/be/38/266fbcbb3d0088862c9bafa8b1b99486691d2945a90b9a7316336a0d9a1b/ruff-0.9.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb7554aca6f842645022fe2d301c264e6925baa708b392867b7a62645304df4", size = 14458585 }, - { url = "https://files.pythonhosted.org/packages/63/a6/47fd0e96990ee9b7a4abda62de26d291bd3f7647218d05b7d6d38af47c30/ruff-0.9.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabc332b7075a914ecea912cd1f3d4370489c8018f2c945a30bcc934e3bc06a6", size = 12419624 }, - { url = "https://files.pythonhosted.org/packages/84/5d/de0b7652e09f7dda49e1a3825a164a65f4998175b6486603c7601279baad/ruff-0.9.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:33866c3cc2a575cbd546f2cd02bdd466fed65118e4365ee538a3deffd6fcb730", size = 11843238 }, - { url = "https://files.pythonhosted.org/packages/9e/be/3f341ceb1c62b565ec1fb6fd2139cc40b60ae6eff4b6fb8f94b1bb37c7a9/ruff-0.9.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:006e5de2621304c8810bcd2ee101587712fa93b4f955ed0985907a36c427e0c2", size = 11484012 }, - { url = "https://files.pythonhosted.org/packages/a3/c8/ff8acbd33addc7e797e702cf00bfde352ab469723720c5607b964491d5cf/ruff-0.9.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ba6eea4459dbd6b1be4e6bfc766079fb9b8dd2e5a35aff6baee4d9b1514ea519", size = 12038494 }, - { url = "https://files.pythonhosted.org/packages/73/b1/8d9a2c0efbbabe848b55f877bc10c5001a37ab10aca13c711431673414e5/ruff-0.9.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:90230a6b8055ad47d3325e9ee8f8a9ae7e273078a66401ac66df68943ced029b", size = 12473639 }, - { url = "https://files.pythonhosted.org/packages/cb/44/a673647105b1ba6da9824a928634fe23186ab19f9d526d7bdf278cd27bc3/ruff-0.9.3-py3-none-win32.whl", hash = "sha256:eabe5eb2c19a42f4808c03b82bd313fc84d4e395133fb3fc1b1516170a31213c", size = 9834353 }, - { url = "https://files.pythonhosted.org/packages/c3/01/65cadb59bf8d4fbe33d1a750103e6883d9ef302f60c28b73b773092fbde5/ruff-0.9.3-py3-none-win_amd64.whl", hash = "sha256:040ceb7f20791dfa0e78b4230ee9dce23da3b64dd5848e40e3bf3ab76468dcf4", size = 10821444 }, - { url = "https://files.pythonhosted.org/packages/69/cb/b3fe58a136a27d981911cba2f18e4b29f15010623b79f0f2510fd0d31fd3/ruff-0.9.3-py3-none-win_arm64.whl", hash = "sha256:800d773f6d4d33b0a3c60e2c6ae8f4c202ea2de056365acfa519aa48acf28e0b", size = 10038168 }, + { url = "https://files.pythonhosted.org/packages/b6/f8/3fafb7804d82e0699a122101b5bee5f0d6e17c3a806dcbc527bb7d3f5b7a/ruff-0.9.4-py3-none-linux_armv6l.whl", hash = "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706", size = 11668400 }, + { url = "https://files.pythonhosted.org/packages/2e/a6/2efa772d335da48a70ab2c6bb41a096c8517ca43c086ea672d51079e3d1f/ruff-0.9.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf", size = 11628395 }, + { url = "https://files.pythonhosted.org/packages/dc/d7/cd822437561082f1c9d7225cc0d0fbb4bad117ad7ac3c41cd5d7f0fa948c/ruff-0.9.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:54499fb08408e32b57360f6f9de7157a5fec24ad79cb3f42ef2c3f3f728dfe2b", size = 11090052 }, + { url = "https://files.pythonhosted.org/packages/9e/67/3660d58e893d470abb9a13f679223368ff1684a4ef40f254a0157f51b448/ruff-0.9.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37c892540108314a6f01f105040b5106aeb829fa5fb0561d2dcaf71485021137", size = 11882221 }, + { url = "https://files.pythonhosted.org/packages/79/d1/757559995c8ba5f14dfec4459ef2dd3fcea82ac43bc4e7c7bf47484180c0/ruff-0.9.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de9edf2ce4b9ddf43fd93e20ef635a900e25f622f87ed6e3047a664d0e8f810e", size = 11424862 }, + { url = "https://files.pythonhosted.org/packages/c0/96/7915a7c6877bb734caa6a2af424045baf6419f685632469643dbd8eb2958/ruff-0.9.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87c90c32357c74f11deb7fbb065126d91771b207bf9bfaaee01277ca59b574ec", size = 12626735 }, + { url = "https://files.pythonhosted.org/packages/0e/cc/dadb9b35473d7cb17c7ffe4737b4377aeec519a446ee8514123ff4a26091/ruff-0.9.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56acd6c694da3695a7461cc55775f3a409c3815ac467279dfa126061d84b314b", size = 13255976 }, + { url = "https://files.pythonhosted.org/packages/5f/c3/ad2dd59d3cabbc12df308cced780f9c14367f0321e7800ca0fe52849da4c/ruff-0.9.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0c93e7d47ed951b9394cf352d6695b31498e68fd5782d6cbc282425655f687a", size = 12752262 }, + { url = "https://files.pythonhosted.org/packages/c7/17/5f1971e54bd71604da6788efd84d66d789362b1105e17e5ccc53bba0289b/ruff-0.9.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4c8772670aecf037d1bf7a07c39106574d143b26cfe5ed1787d2f31e800214", size = 14401648 }, + { url = "https://files.pythonhosted.org/packages/30/24/6200b13ea611b83260501b6955b764bb320e23b2b75884c60ee7d3f0b68e/ruff-0.9.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc5f1d7afeda8d5d37660eeca6d389b142d7f2b5a1ab659d9214ebd0e025231", size = 12414702 }, + { url = "https://files.pythonhosted.org/packages/34/cb/f5d50d0c4ecdcc7670e348bd0b11878154bc4617f3fdd1e8ad5297c0d0ba/ruff-0.9.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faa935fc00ae854d8b638c16a5f1ce881bc3f67446957dd6f2af440a5fc8526b", size = 11859608 }, + { url = "https://files.pythonhosted.org/packages/d6/f4/9c8499ae8426da48363bbb78d081b817b0f64a9305f9b7f87eab2a8fb2c1/ruff-0.9.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6c634fc6f5a0ceae1ab3e13c58183978185d131a29c425e4eaa9f40afe1e6d6", size = 11485702 }, + { url = "https://files.pythonhosted.org/packages/18/59/30490e483e804ccaa8147dd78c52e44ff96e1c30b5a95d69a63163cdb15b/ruff-0.9.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:433dedf6ddfdec7f1ac7575ec1eb9844fa60c4c8c2f8887a070672b8d353d34c", size = 12067782 }, + { url = "https://files.pythonhosted.org/packages/3d/8c/893fa9551760b2f8eb2a351b603e96f15af167ceaf27e27ad873570bc04c/ruff-0.9.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d612dbd0f3a919a8cc1d12037168bfa536862066808960e0cc901404b77968f0", size = 12483087 }, + { url = "https://files.pythonhosted.org/packages/23/15/f6751c07c21ca10e3f4a51ea495ca975ad936d780c347d9808bcedbd7182/ruff-0.9.4-py3-none-win32.whl", hash = "sha256:db1192ddda2200671f9ef61d9597fcef89d934f5d1705e571a93a67fb13a4402", size = 9852302 }, + { url = "https://files.pythonhosted.org/packages/12/41/2d2d2c6a72e62566f730e49254f602dfed23019c33b5b21ea8f8917315a1/ruff-0.9.4-py3-none-win_amd64.whl", hash = "sha256:05bebf4cdbe3ef75430d26c375773978950bbf4ee3c95ccb5448940dc092408e", size = 10850051 }, + { url = "https://files.pythonhosted.org/packages/c6/e6/3d6ec3bc3d254e7f005c543a661a41c3e788976d0e52a1ada195bd664344/ruff-0.9.4-py3-none-win_arm64.whl", hash = "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41", size = 10078251 }, ] [[package]]