diff --git a/docs/drafts/gitea-runner-on-demand.md b/docs/drafts/gitea-runner-on-demand.md index ed14d58..59cc7e5 100644 --- a/docs/drafts/gitea-runner-on-demand.md +++ b/docs/drafts/gitea-runner-on-demand.md @@ -161,10 +161,11 @@ echo "$(date -u +%FT%TZ) $state containers=$busy_count" \ дёргает Compute REST: ```python -TOKEN_URL = "http://169.254.169.254/computeMetadata/v1/instance/" \ - "service-accounts/default/token" -ID_URL = "http://169.254.169.254/computeMetadata/v1/instance/id" -HEADERS = {"Metadata-Flavor": "Google"} +TOKEN_URL = ( + "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" +) +ID_URL = "http://169.254.169.254/computeMetadata/v1/instance/id" +HEADERS = {"Metadata-Flavor": "Google"} token = requests.get(TOKEN_URL, headers=HEADERS).json()["access_token"] instance_id = requests.get(ID_URL, headers=HEADERS).text diff --git a/files/backups/backup-all.py b/files/backups/backup-all.py index beca7e3..d2fbd1f 100644 --- a/files/backups/backup-all.py +++ b/files/backups/backup-all.py @@ -207,7 +207,9 @@ def measure_app_sizes(paths: list[Path]) -> dict[str, int]: cmd = [DUST_BIN, "--output-json", "--output-format", "b", "--depth", "0"] cmd += ["--no-progress", *(str(path) for path in paths)] try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=600) + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=600, check=False + ) except (OSError, subprocess.TimeoutExpired) as exc: logger.warning("Failed to run %s: %s", DUST_BIN, exc) return {} @@ -410,7 +412,9 @@ class ResticStorage(Storage): def __run_step(self, step: str, cmd: list[str], env: dict[str, str]) -> str | None: """Run a single restic command. Return None on success or error text.""" - result = subprocess.run(cmd, env=env, capture_output=True, text=True) + result = subprocess.run( + cmd, env=env, capture_output=True, text=True, check=False + ) if result.returncode != 0: error = result.stderr.strip() or result.stdout.strip() or "no output" @@ -604,7 +608,9 @@ class BackupManager: logger.info("Phases (forced): %s", ", ".join(self.forced_phases)) return self.forced_phases - phases = self.schedule.due_phases(datetime.now()) + # Расписание фаз считается по календарным суткам локального времени + # сервера; aware-время в UTC сдвинуло бы границу суток. + phases = self.schedule.due_phases(datetime.now()) # noqa: DTZ005 logger.info("Phases (scheduled): %s", ", ".join(phases)) return phases @@ -754,6 +760,7 @@ class BackupManager: capture_output=True, text=True, timeout=3600, # 1 hour timeout + check=False, ) if result.returncode == 0: @@ -771,8 +778,9 @@ class BackupManager: logger.error(error_msg) self.errors.append(f"App {username}: {error_msg}") return False - except Exception as e: - error_msg = f"Failed to run backup script {script_path}: {str(e)}" + # Падение одного дампа не должно ронять весь прогон. + except Exception as e: # noqa: BLE001 + error_msg = f"Failed to run backup script {script_path}: {e!s}" logger.error(error_msg) self.errors.append(f"App {username}: {error_msg}") return False @@ -841,7 +849,8 @@ class BackupManager: for notificator in self.notifiers: try: notificator.send(title, message) - except Exception as e: + # Недоступность одного нотификатора не должна ронять остальные. + except Exception as e: # noqa: BLE001 logger.error("Failed to send notification: %s", e) @@ -981,7 +990,8 @@ def main() -> None: except KeyboardInterrupt: logger.info("Backup process interrupted by user") sys.exit(130) - except Exception as e: + # Верхний уровень: любая неожиданная ошибка уходит в лог, а не в traceback. + except Exception as e: # noqa: BLE001 logger.error("Unexpected error in backup process: %s", e) sys.exit(1) diff --git a/files/keep-files.py b/files/keep-files.py index dca8d8c..7016a59 100644 --- a/files/keep-files.py +++ b/files/keep-files.py @@ -42,8 +42,8 @@ def main() -> None: try: filepath.unlink() print(f"Deleted: {filename}") - except Exception as e: - print(f"Error deleting {filename}: {str(e)}") + except Exception as e: # noqa: BLE001 + print(f"Error deleting {filename}: {e!s}") if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 6fed1ee..fb41075 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,10 +11,7 @@ dependencies = [ "invoke>=2.2.1", "pyrefly>=1.2.0", "requests>=2.32.5", - # 0.16 расширил набор правил по умолчанию (PLW, EXE, BLE, TRY, DTZ, RUF) - # и начал форматировать Python внутри markdown. Обновление отложено до - # отдельного захода с правкой кода: 29 срабатываний в скриптах и tasks.py. - "ruff>=0.15.2,<0.16", + "ruff>=0.16.6", "types-croniter>=6.0.0", "types-requests>=2.32.4.20260107", "yamllint>=1.37.1", @@ -52,7 +49,14 @@ extend-select = [ ] # Any в сигнатурах используется осознанно: разбор конфигов из TOML # и параметры сторонних API без стабов типов. -ignore = ["ANN401"] +# +# EXE001: бит исполняемости в репозитории ничего не решает — скрипты из files/ +# уезжают на сервер через Ansible с явным mode 0755, а tasks.py и scripts/ +# запускаются через inv и python3. +# TRY004: ValueError в скриптах — это ошибка в написанном руками конфиге +# (config.toml), а не передача не того типа в API; TypeError вводил бы в +# заблуждение. +ignore = ["ANN401", "EXE001", "TRY004"] [tool.pyrefly] # Без секции pyrefly ругается на отсутствие конфига; здесь же задаём, diff --git a/scripts/smtp-convert-secret-key-to-password.py b/scripts/smtp-convert-secret-key-to-password.py index 1a679d1..01ab0d2 100644 --- a/scripts/smtp-convert-secret-key-to-password.py +++ b/scripts/smtp-convert-secret-key-to-password.py @@ -23,11 +23,10 @@ Yandex Cloud Postbox использует AWS-совместимый API, и д Ссылка: https://yandex.cloud/ru/docs/postbox/operations/send-email """ -import hmac -import hashlib -import base64 import argparse - +import base64 +import hashlib +import hmac # These values are required to calculate the signature. Do not change them. DATE = "20230926" diff --git a/scripts/smtp-send-test-email.py b/scripts/smtp-send-test-email.py index 33dd463..d65bd0d 100644 --- a/scripts/smtp-send-test-email.py +++ b/scripts/smtp-send-test-email.py @@ -10,8 +10,8 @@ SMTP Test Email Sender for Yandex Cloud Postbox python3 smtp-send-test-email.py --login "your-login" --password "smtp-password" --to "recipient@example.com" """ -import smtplib import argparse +import smtplib import sys from email.message import EmailMessage @@ -61,7 +61,7 @@ def main() -> None: try: send_test_email(args.login, args.password, args.to) - except Exception as e: + except Exception as e: # noqa: BLE001 print(f"Error sending email: {e}", file=sys.stderr) sys.exit(1) diff --git a/tasks.py b/tasks.py index 808bb39..228676a 100644 --- a/tasks.py +++ b/tasks.py @@ -129,7 +129,7 @@ def edit_encrypted(ctx: Context, path: str) -> None: # переводит лишь в cbreak — ixon остаётся включённым, и Ctrl+S/Ctrl+Q съедает # драйвер терминала как XOFF/XON, до редактора они не доходят. subprocess # отдаёт редактору настоящий tty, и его raw-режим гасит ixon сам. - result = subprocess.run(["uv", "run", "ansible-vault", "edit", path]) + result = subprocess.run(["uv", "run", "ansible-vault", "edit", path], check=False) if result.returncode != 0: raise Exit( f"ansible-vault edit: код возврата {result.returncode}", @@ -173,7 +173,10 @@ def roles_status(ctx: Context) -> None: print(f"{role}: missing in shared") drift = True elif ( - subprocess.run(["diff", "-rq", f"roles/{role}", str(canon)]).returncode != 0 + subprocess.run( + ["diff", "-rq", f"roles/{role}", str(canon)], check=False + ).returncode + != 0 ): print(f"{role}: differs") drift = True @@ -206,11 +209,14 @@ def roles_push(ctx: Context) -> None: raise Exit(f"Локальной роли нет: {local}", code=1) differs = ( canon.is_dir() - and subprocess.run(["diff", "-rq", str(local), str(canon)]).returncode != 0 + and subprocess.run( + ["diff", "-rq", str(local), str(canon)], check=False + ).returncode + != 0 ) if differs: # Защита от затирания более свежего канона устаревшей копией. - subprocess.run(["diff", "-r", str(canon), str(local)]) + subprocess.run(["diff", "-r", str(canon), str(local)], check=False) answer = input( f"Канон '{role}' отличается. Перезаписать его копией из этого репо? [y/N] " ) @@ -252,6 +258,7 @@ def login_as_app(ctx: Context, app: str) -> None: subprocess.run( f"""ssh {_remote_user()}@{_remote_host()} -t 'sudo -iu {app} bash -c "cd {app_dir} && exec bash -il"'""", shell=True, + check=False, ) diff --git a/uv.lock b/uv.lock index 1867309..176a9f9 100644 --- a/uv.lock +++ b/uv.lock @@ -614,7 +614,7 @@ requires-dist = [ { name = "invoke", specifier = ">=2.2.1" }, { name = "pyrefly", specifier = ">=1.2.0" }, { name = "requests", specifier = ">=2.32.5" }, - { name = "ruff", specifier = ">=0.15.2,<0.16" }, + { name = "ruff", specifier = ">=0.16.6" }, { name = "types-croniter", specifier = ">=6.0.0" }, { name = "types-requests", specifier = ">=2.32.4.20260107" }, { name = "yamllint", specifier = ">=1.37.1" }, @@ -927,27 +927,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.22" +version = "0.16.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7c/6adb35d70e7c027e308274557901c7e00fb3407750faf3620c184ae058cb/ruff-0.16.6.tar.gz", hash = "sha256:dcf8a73d2ff77e99dde91244b4da16feba7f14e6beeb4015dee7c5a909e99050", size = 4921251, upload-time = "2026-09-03T16:57:29.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, - { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, - { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, - { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, - { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, - { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, - { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, - { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, - { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/9cc1b79639e284ec103f43c88c644db4eb58cbd0ea1ca11f1193435369ac/ruff-0.16.6-py3-none-linux_armv6l.whl", hash = "sha256:61c368c26bf8e973e5ab14a2772de587bc068ea3f9a277f673380749b4898fb8", size = 10015638, upload-time = "2026-09-03T16:56:40.986Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/627d342ef727ea7794edf74fe23d60a074b02c3acc2e9436684e782286ca/ruff-0.16.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ecf4f068e2e123e43a26e9db4e19524cc56563912404e83bbfca375757e45a32", size = 10220762, upload-time = "2026-09-03T16:56:44.681Z" }, + { url = "https://files.pythonhosted.org/packages/43/d9/b75668ce41e4c8d073d18d6d08672ba6906ce45d5c06ea4fdb2e84ce3853/ruff-0.16.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:99b62ea33baf130f50368798d841f0d95527b6d817bf31817b65dd058f1d314c", size = 9835082, upload-time = "2026-09-03T16:56:47.142Z" }, + { url = "https://files.pythonhosted.org/packages/99/97/123ab10b05cde889c107c20f5a9774955104b5552796a2a8584b089ae8eb/ruff-0.16.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fbf89013f2bb3f6835a6038ff658dc8a1b38c98dc8e724b964168ad4e881876", size = 9949304, upload-time = "2026-09-03T16:56:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/58/a4a2c59dd2e5b85929c912d9cac3056eb9ee8c7e75e9b9fe3e109174966b/ruff-0.16.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56a67065e22efa6bc4d498299d3bb06c0c90aace8fac2068b5a12f9dc4d8d51d", size = 9840612, upload-time = "2026-09-03T16:56:52.368Z" }, + { url = "https://files.pythonhosted.org/packages/61/6a/ff8c8626a786c4f49d48ced4a752dadbca65f5263005f9c2416578194694/ruff-0.16.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e25cc89174874b176a157e4428d66761c2c0c006654419bf384f967f361ff1b1", size = 10543465, upload-time = "2026-09-03T16:56:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bb/c47535923365f337b82e28192e4e9eef2176511007cfd99a62fc22df5dad/ruff-0.16.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700580ed5303723cb3c11c2f1d2a8913ce77b7ea86646dddb887f5417a9ba70", size = 11267576, upload-time = "2026-09-03T16:56:57.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/50/e5119a5212b5cd63b51e1f4b25e7bd636a6668fc069a3160b108ad7e3c16/ruff-0.16.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15f1d0b6e165a6e56567befb6629f8209271311d990bae0f37e6d065035ef5f3", size = 10781993, upload-time = "2026-09-03T16:57:00.666Z" }, + { url = "https://files.pythonhosted.org/packages/8b/98/083d8b4ef3c51a0d19db84367791cbe9f44e4b53343d19dfa83556e1cd9a/ruff-0.16.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d72c591a96986ee4268860e2b7235082129ca5e4cb9cbba653a4b57c11893757", size = 10317748, upload-time = "2026-09-03T16:57:03.428Z" }, + { url = "https://files.pythonhosted.org/packages/9a/29/68f7ff2c5ad95f19f00627ac2de95644e25fe47371ea60b2db1fd952315e/ruff-0.16.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:65a006baa18f33324325814c864daef03541d51564b98c517610ea756ab7003e", size = 10540096, upload-time = "2026-09-03T16:57:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f9/79a8f6de85968641d68a7863aeec577551924ef066a990a48ff93167beab/ruff-0.16.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cd02a7bf1a21a8735228a3e8c95a9dc5cf86bd2a52194f4aaae2a5755b4de0f4", size = 10100494, upload-time = "2026-09-03T16:57:09.194Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e8/b81a22d9b90c00b892ccf2fa2ac36fa95de4c13ab85aea3e73795cfe4651/ruff-0.16.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:31b36f1e5ad85e0737f09d2be4e512e2e283583c14015da3b9dc07359ac0fc88", size = 9843663, upload-time = "2026-09-03T16:57:12.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/aa/54f516ec5e5a11c4afdceb1c454ebb054ffb96e4f4a1705580b4346abd35/ruff-0.16.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:61029b4ab4aa723fd3064fab96b1d814492596bf0c792679fffcbde1e1679953", size = 10282461, upload-time = "2026-09-03T16:57:15.077Z" }, + { url = "https://files.pythonhosted.org/packages/52/0b/38d0aa8aa32372b96dc44f97b22e576c4147808271aab7b2cb1e353d4445/ruff-0.16.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9ac8998457832c2061709d900856b7ad271dace0cb41f346588d540162bfa718", size = 10728808, upload-time = "2026-09-03T16:57:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/9e274e24eeb027640ffc7442f21239f16d17f47acec15ae34f32e03a5c79/ruff-0.16.6-py3-none-win32.whl", hash = "sha256:0b87d9d16fcb63e8018423ca1d50b7260f15cb2da33e30db4baad4183a948c25", size = 10049212, upload-time = "2026-09-03T16:57:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/31/72472449414223ed1a2da236b992adbb1a2ae59e34794574810f60ce068e/ruff-0.16.6-py3-none-win_amd64.whl", hash = "sha256:10d21c51c3495d8eaea7b703a16592117ea6eb1d649e36335aa965ff1173eb39", size = 10556402, upload-time = "2026-09-03T16:57:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/fc/07/d781f8f8e1ac24bef9f3269cf62ffb1407ca24c3a8f12e5e22874f90528c/ruff-0.16.6-py3-none-win_arm64.whl", hash = "sha256:7a976c79b958f94e50a022a19f0f8c87387448020935ec14fc74331bd0a7f2c5", size = 10412850, upload-time = "2026-09-03T16:57:26.416Z" }, ] [[package]]