From 62fa19059c492d2f03a89fb878b34a0ba3c03add Mon Sep 17 00:00:00 2001 From: blakeNaccarato Date: Wed, 29 Jan 2025 13:54:09 -0800 Subject: [PATCH] Implement environment syncing in VSCode settings --- .pre-commit-config.yaml | 2 +- .vscode/settings.json | 48 +++++- .vscode/tasks.json | 160 +++++------------- Invoke-Just.ps1 | 8 +- Invoke-Uv.ps1 | 8 +- dev.ps1 | 106 +++++++----- docs/conf.py | 2 +- packages/_dev/boilercv_dev/__init__.py | 10 ++ packages/_dev/boilercv_dev/__main__.py | 5 +- packages/_dev/boilercv_dev/tests/__init__.py | 2 +- .../_dev/boilercv_dev/tools/environment.py | 20 +-- packages/pipeline/boilercv_pipeline/parser.py | 2 +- pyproject.toml | 16 +- requirements/requirements_dev.txt | 2 +- tests/conftest.py | 6 +- uv.lock | 46 ++--- 16 files changed, 218 insertions(+), 225 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40d8ae61..d0576998 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -144,7 +144,7 @@ repos: hooks: - id: taplo-format - repo: "https://github.com/charliermarsh/ruff-pre-commit" - rev: "v0.5.1" + rev: "v0.9.3" hooks: - id: "ruff" args: ["--extend-fixable", "PIE790"] diff --git a/.vscode/settings.json b/.vscode/settings.json index 608e0c98..74b2226d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - // * ----------------------------------------------------------------------------- * # + //* ------------------------------------------------------------------------------ *// //! Schema "yaml.schemas": { // ? `boilercv` @@ -15,7 +15,7 @@ "pipeline/boilercv_pipeline/settings_schema.json": "packages/pipeline/boilercv_pipeline/settings.yaml", "pipeline/boilercv_pipeline/settings_plugin_schema.json": "packages/pipeline/boilercv_pipeline/settings_plugin.yaml" }, - // * ----------------------------------------------------------------------------- * # + //* ------------------------------------------------------------------------------ *// //! Terminal //? Use PowerShell on all platforms, facilitates running template scripts "terminal.integrated.defaultProfile.windows": "PowerShell", @@ -100,8 +100,6 @@ "source.organizeImports": "explicit" }, //! Extensions - //* autoDocstring - "autoDocstring.docstringFormat": "numpy-notypes", //* Better Comments "better-comments.tags": [ { @@ -228,7 +226,11 @@ "[markdown]": { "editor.defaultFormatter": "DavidAnson.vscode-markdownlint" }, - //* Prettier (JSON, JSONC, YAML) + //* Prettier + "[github-actions-workflow]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.wordWrap": "off" + }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, @@ -243,10 +245,6 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.wordWrap": "off" }, - "[github-actions-workflow]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.wordWrap": "off" - }, "yaml.format.printWidth": 88, //* Justfile (JUST) "[just]": { "editor.tabSize": 2, "editor.wordWrap": "off" }, @@ -269,6 +267,38 @@ "editor.wordWrap": "off" }, "evenBetterToml.taplo.configFile.path": ".taplo.toml", + //* Automatically synchronized environment variables + //! PLEASE MODIFY `pyproject.toml:tool.boilercv_dev.env` AND RUN `./Invoke-Uv -Sync` + "terminal.integrated.env.linux": { + "COVERAGE_CORE": "sysmon", + "JUPYTER_PLATFORM_DIRS": "1", + "PYDEVD_DISABLE_FILE_VALIDATION": "1", + "PYRIGHT_PYTHON_PYLANCE_VERSION": "2024.6.1", + "PYTHONIOENCODING": "utf-8:strict", + "PYTHONUTF8": "1", + "PYTHONWARNDEFAULTENCODING": "1", + "PYTHONWARNINGS": "ignore" + }, + "terminal.integrated.env.osx": { + "COVERAGE_CORE": "sysmon", + "JUPYTER_PLATFORM_DIRS": "1", + "PYDEVD_DISABLE_FILE_VALIDATION": "1", + "PYRIGHT_PYTHON_PYLANCE_VERSION": "2024.6.1", + "PYTHONIOENCODING": "utf-8:strict", + "PYTHONUTF8": "1", + "PYTHONWARNDEFAULTENCODING": "1", + "PYTHONWARNINGS": "ignore" + }, + "terminal.integrated.env.windows": { + "COVERAGE_CORE": "sysmon", + "JUPYTER_PLATFORM_DIRS": "1", + "PYDEVD_DISABLE_FILE_VALIDATION": "1", + "PYRIGHT_PYTHON_PYLANCE_VERSION": "2024.6.1", + "PYTHONIOENCODING": "utf-8:strict", + "PYTHONUTF8": "1", + "PYTHONWARNDEFAULTENCODING": "1", + "PYTHONWARNINGS": "ignore" + }, //! Other //? Other automatically added settings below "": "" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ba56c994..7fbdb385 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,8 +1,7 @@ -// Run tasks like `pwsh -Command "./Invoke-Uv.ps1 && {task}` to run profile-like -// commands without requiring users to modify global profile. - { "version": "2.0.0", + "type": "shell", + "problemMatcher": ["$python"], "inputs": [ { "id": "stage", @@ -75,101 +74,73 @@ "tasks": [ { "label": "just: generate correlations", - "type": "shell", "command": "./Invoke-Just.ps1 generate-correlations ${file} ${input:correlations}", - "icon": { "id": "file-code" }, - "problemMatcher": [] + "icon": { "id": "file-code" } }, { "label": "just: generate correlation docs", - "type": "shell", "command": "./Invoke-Just.ps1 generate-correlation-docs", - "icon": { "id": "file-code" }, - "problemMatcher": [] + "icon": { "id": "file-code" } }, { "label": "just: update binder", - "type": "shell", "command": "./Invoke-Just.ps1 update-binder", - "icon": { "id": "git-commit" }, - "problemMatcher": [] + "icon": { "id": "git-commit" } }, { "label": "just: sync local dev configs", - "type": "shell", "command": "./Invoke-Just.ps1 sync-local-dev-configs", - "icon": { "id": "file-code" }, - "problemMatcher": [] + "icon": { "id": "file-code" } }, { "label": "just: remove empty data folders", - "type": "shell", "command": "./Invoke-Just.ps1 remove-empty-data-folders", - "icon": { "id": "terminal-powershell" }, - "problemMatcher": [] + "icon": { "id": "terminal-powershell" } }, { "label": "just: patch notebooks", - "type": "shell", "command": "./Invoke-Just.ps1 patch-notebooks", - "icon": { "id": "graph" }, - "problemMatcher": [] + "icon": { "id": "graph" } }, { "label": "just: boilercv preview write", - "type": "shell", "command": "./Invoke-Just.ps1 boilercv-preview-write ${file}", - "icon": { "id": "repo" }, - "problemMatcher": [] + "icon": { "id": "repo" } }, { "label": "just: boilercv debug preview write", - "type": "shell", "command": "./Invoke-Just.ps1 boilercv-debug-preview-write ${file}", - "icon": { "id": "repo" }, - "problemMatcher": [] + "icon": { "id": "repo" } }, { "label": "just: boilercv write", - "type": "shell", "command": "./Invoke-Just.ps1 boilercv-write ${file}", - "icon": { "id": "repo" }, - "problemMatcher": [] + "icon": { "id": "repo" } }, { "label": "just: boilercv preview", - "type": "shell", "command": "./Invoke-Just.ps1 boilercv-preview ${input:preview}", - "icon": { "id": "graph" }, - "problemMatcher": [] + "icon": { "id": "graph" } }, { "label": "dvc: repro", - "type": "shell", "command": "./Invoke-Uv.ps1 dvc repro ${input:stage}", - "icon": { "id": "graph" }, - "problemMatcher": [] + "icon": { "id": "graph" } }, { "label": "dvc: repro force", - "type": "shell", "command": "./Invoke-Uv.ps1 dvc repro --force ${input:stage}", - "icon": { "id": "graph" }, - "problemMatcher": [] + "icon": { "id": "graph" } }, { "label": "just: dvc dag", - "type": "shell", "command": "./Invoke-Just.ps1 dvc-dag", - "icon": { "id": "graph" }, - "problemMatcher": [] + "icon": { "id": "graph" } }, { "label": "just: sync dvc", - "type": "shell", "command": "./Invoke-Just.ps1 pipeline-sync-dvc", - "icon": { "id": "graph" }, - "problemMatcher": [] + "icon": { "id": "graph" } }, // * -------------------------------------------------------------------------- * // @@ -177,8 +148,8 @@ { "label": "wsl: Copy PID of Python Debugger", - "type": "shell", "command": "./Invoke-Uv.ps1 && ps aux | grep python | grep --max-count 1 -- --adapter-access-token | grep --only-matching --perl-regexp 'user\\s+\\d+' | grep --only-matching --perl-regexp '\\d+' | clip.exe", + "icon": { "id": "terminal-linux" }, "group": { "kind": "test", "isDefault": true @@ -187,9 +158,7 @@ "close": false, "focus": true, "reveal": "always" - }, - "icon": { "id": "terminal-linux" }, - "problemMatcher": [] + } }, // * -------------------------------------------------------------------------- * // @@ -197,21 +166,16 @@ { "label": "setup: Sync with template", - "type": "shell", "command": ". ./dev.ps1 && Sync-Template ${input:templateOptions}", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, { "label": "setup: Sync with specific template ref", - "type": "shell", "command": ". ./dev.ps1 && Sync-Template ${input:ref} ${input:templateOptions}", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, { "label": "setup: just: sync contrib", - "type": "shell", "command": "if (!$Env:DEVCONTAINER) {./Invoke-Just.ps1 sync-contrib}", "icon": { "id": "file-symlink-directory" }, "problemMatcher": [], @@ -221,40 +185,30 @@ }, { "label": "setup: Initialize repository", - "type": "shell", "command": ". ./dev.ps1 && Initialize-Repo", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, { "label": "setup: Remove *.rej", - "type": "shell", "command": "Get-ChildItem -Recurse -Filter *.rej | Remove-Item", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, { "label": "setup: Initialize Windows machine", - "type": "shell", "options": { "shell": { "executable": "powershell" } }, "command": ". ./dev.ps1 && Initialize-Windows", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, { "label": "setup: Initialize Linux/MacOS machine", - "type": "shell", "options": { "shell": { "executable": "bash" } }, "command": "scripts/Initialize-LinuxMacOS.sh", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, { "label": "setup: Finish initializing machine (cross-platform)", - "type": "shell", "command": ". ./dev.ps1 && Initialize-Machine", - "icon": { "id": "file-symlink-directory" }, - "problemMatcher": [] + "icon": { "id": "file-symlink-directory" } }, // * -------------------------------------------------------------------------- * // @@ -262,115 +216,83 @@ { "label": "task: pre-commit", - "type": "shell", "command": "./Invoke-Uv.ps1 pre-commit run --verbose", - "icon": { "id": "git-commit" }, - "problemMatcher": [] + "icon": { "id": "git-commit" } }, { "label": "task: pre-commit (all)", - "type": "shell", "command": "./Invoke-Uv.ps1 pre-commit run --all-files --verbose", - "icon": { "id": "git-commit" }, - "problemMatcher": [] + "icon": { "id": "git-commit" } }, { "label": "task: pre-commit (clean notebooks)", - "type": "shell", "command": "./Invoke-Uv.ps1 pre-commit run --all-files nb-clean --verbose", - "icon": { "id": "git-commit" }, - "problemMatcher": [] + "icon": { "id": "git-commit" } }, { "label": "task: pre-commit (clean and format notebooks)", - "type": "shell", "command": "./Dev.ps1 && iuv pre-commit run --all-files patch-notebooks --verbose && iuv pre-commit run --all-files nb-clean --verbose && iuv pre-commit run --all-files ruff --verbose && iuv pre-commit run --all-files ruff-format --verbose", - "icon": { "id": "git-commit" }, - "problemMatcher": [] + "icon": { "id": "git-commit" } }, { "label": "task: Rebase back to fork", - "type": "shell", "command": "git rebase -i --fork-point main", - "icon": { "id": "git-branch" }, - "problemMatcher": [] + "icon": { "id": "git-branch" } }, { "label": "task: Show tree of packages requesting a dependency", - "type": "shell", "command": "./Invoke-Uv.ps1 pipdeptree --reverse --packages ${input:dependency}", - "icon": { "id": "versions" }, - "problemMatcher": [] + "icon": { "id": "versions" } }, { "label": "task: Run pytest with coverage", - "type": "shell", "command": "./Invoke-Uv.ps1 pytest --cov --cov-config pyproject.toml --cov-report xml", - "icon": { "id": "check" }, - "problemMatcher": [] + "icon": { "id": "check" } }, { "label": "task: Run ruff", - "type": "shell", "command": ". ./dev.ps1 && iuv ruff check . && iuv ruff format .", - "icon": { "id": "check" }, - "problemMatcher": [] + "icon": { "id": "check" } }, { "label": "task: Run pyright", - "type": "shell", "command": "./Invoke-Uv.ps1 pyright", - "icon": { "id": "check" }, - "problemMatcher": [] + "icon": { "id": "check" } }, { "label": "task: Build docs", - "type": "shell", "command": "./Invoke-Just.ps1 build-docs", - "icon": { "id": "book" }, - "problemMatcher": [] + "icon": { "id": "book" } }, { "label": "task: Profile this file", - "type": "shell", "command": "./Invoke-Uv.ps1 cProfile -o .prof ${file}", - "icon": { "id": "graph-line" }, - "problemMatcher": [] + "icon": { "id": "graph-line" } }, { "label": "task: View profile results with snakeviz", - "type": "shell", "command": "./Invoke-Uv.ps1 snakeviz .prof", - "icon": { "id": "graph-line" }, - "problemMatcher": [] + "icon": { "id": "graph-line" } }, { "label": "task: Bump version", - "type": "shell", "command": ". ./dev.ps1 && uvx copier@9.2.0 update --vcs-ref=HEAD --defaults --data project_version='${input:version}' && iuv towncrier build --yes --version '${input:version}' && git add . && git commit -m '${input:version}'", - "icon": { "id": "tag" }, - "problemMatcher": [] + "icon": { "id": "tag" } }, { "label": "task: Release version", - "type": "shell", "command": ". ./dev.ps1 && Invoke-Uv && ($Version = (Get-Content '.copier-answers.yml' | Find-Pattern '^project_version:\\s(.+)$')) && git tag --sign -m $Version $Version && git push", - "icon": { "id": "tag" }, - "problemMatcher": [] + "icon": { "id": "tag" } }, { "label": "task: Update changelog", - "type": "shell", "command": "./Invoke-Uv.ps1 -m dev add-change ${input:changeType}", - "icon": { "id": "tag" }, - "problemMatcher": [] + "icon": { "id": "tag" } }, { "label": "task: Update changelog with the latest commit's message", - "type": "shell", "command": "./Invoke-Uv.ps1 towncrier create +$((Get-Date).ToUniversalTime().ToString('o').Replace(':','-')).change.md --content $($(git log -1 --format='%s') + ' ([' + $(git rev-parse --short HEAD) + '](https://github.com/softboiler/boilercv/commit/' + $(git rev-parse HEAD) + '))\n')", - "icon": { "id": "tag" }, - "problemMatcher": [] + "icon": { "id": "tag" } } ] } diff --git a/Invoke-Just.ps1 b/Invoke-Just.ps1 index 94093bb1..a801bbf5 100644 --- a/Invoke-Just.ps1 +++ b/Invoke-Just.ps1 @@ -8,7 +8,7 @@ Param( [switch]$High, [switch]$Build, [switch]$Force, - [switch]$CI, + [switch]$_CI, [switch]$Locked, [switch]$Devcontainer, [string]$PythonVersion = (Get-Content '.python-version'), @@ -18,7 +18,7 @@ Param( Begin { . ./dev.ps1 - $CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)) + $_CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)) $InvokeUvArgs = @{ Sync = $Sync Update = $Update @@ -26,8 +26,8 @@ Begin { High = $High Build = $Build Force = $Force - CI = $CI - Locked = $CI + _CI = $_CI + Locked = $_CI Devcontainer = (New-Switch $Env:SYNC_ENV_DISABLE_DEVCONTAINER (New-Switch $Env:DEVCONTAINER)) PythonVersion = $PythonVersion PylanceVersion = $PylanceVersion diff --git a/Invoke-Uv.ps1 b/Invoke-Uv.ps1 index fab6479b..ead058b8 100644 --- a/Invoke-Uv.ps1 +++ b/Invoke-Uv.ps1 @@ -8,7 +8,7 @@ Param( [switch]$High, [switch]$Build, [switch]$Force, - [switch]$CI, + [switch]$_CI, [switch]$Locked, [switch]$Devcontainer, [string]$PythonVersion = (Get-Content '.python-version'), @@ -18,7 +18,7 @@ Param( Begin { . ./dev.ps1 - $CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)) + $_CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)) $InvokeUvArgs = @{ Sync = $Sync Update = $Update @@ -26,8 +26,8 @@ Begin { High = $High Build = $Build Force = $Force - CI = $CI - Locked = $CI + _CI = $_CI + Locked = $_CI Devcontainer = (New-Switch $Env:SYNC_ENV_DISABLE_DEVCONTAINER (New-Switch $Env:DEVCONTAINER)) PythonVersion = $PythonVersion PylanceVersion = $PylanceVersion diff --git a/dev.ps1 b/dev.ps1 index 3b43a00b..e13e3af6 100644 --- a/dev.ps1 +++ b/dev.ps1 @@ -39,11 +39,11 @@ function Find-Pattern { <#.SYNOPSIS Find the first match to a pattern in a string.#> Param( - [Parameter(Mandatory)][string]$Pattern, + [Parameter(Mandatory)][string]$Pat, [Parameter(Mandatory, ValueFromPipeline)][string]$String ) process { - if ($Groups = ($String | Select-String -Pattern $Pattern).Matches.Groups) { + if ($Groups = ($String | Select-String -Pattern $Pat).Matches.Groups) { return $Groups[1].value } } @@ -71,7 +71,6 @@ function New-Switch { Param($Cond = $False, $Alt = $False) return [switch]($Cond ? $True : $Alt) } - function Invoke-Uv { <#.SYNOPSIS Invoke `uv`.#> @@ -83,15 +82,16 @@ function Invoke-Uv { [switch]$High, [switch]$Build, [switch]$Force, - [switch]$CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)), - [switch]$Locked = $CI, + # Mangled to avoid its alias shadowing common Python flag `-c` + [switch]$_CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)), + [switch]$Locked = $_CI, [switch]$Devcontainer = (New-Switch $Env:SYNC_ENV_DISABLE_DEVCONTAINER (New-Switch $Env:DEVCONTAINER)), [string]$PythonVersion = (Get-Content '.python-version'), [string]$PylanceVersion = (Get-Content '.pylance-version'), [Parameter(ValueFromPipeline, ValueFromRemainingArguments)][string[]]$Run ) Begin { - if (!$CI) { + if (!$_CI) { # ? Install or update `uv` if ($Update -or !(Get-Command 'uv' -ErrorAction 'Ignore')) { Install-Uv -Update } else { Install-Uv } @@ -100,7 +100,7 @@ function Invoke-Uv { Remove-Item git submodule update --init --merge } - if ($CI -or $Sync) { + if ($_CI -or $Sync) { # ? Sync the environment if (!(Test-Path 'requirements')) { New-Item 'requirements' -ItemType 'Directory' @@ -131,46 +131,33 @@ function Invoke-Uv { $Env:ENV_SYNCED = $null Enter-Venv } - elseif ($CI -or $Force -or !$Env:ENV_SYNCED) { + elseif ($_CI -or $Force -or !$Env:ENV_SYNCED) { + + # ? Avoid reentry + $Env:ENV_SYNCED = $True + # ? Sync the environment uv sync $LockedArg --python $PythonVersion uv export $LockedArg $FrozenArg --no-hashes --python $PythonVersion | Set-Content "$PWD/requirements/requirements_dev.txt" - if ($CI) { - Add-Content $Env:GITHUB_PATH ("$PWD/.venv/bin", "$PWD/.venv/scripts") - } - $Env:ENV_SYNCED = $True Enter-Venv - # ? Sync `.env` and set environment variables from `pyproject.toml` - dev 'sync-environment-variables' - Get-Content ($Env:GITHUB_ENV ? $Env:GITHUB_ENV : "$PWD/.env") | - Select-String -Pattern '^(.+?)=(.+)$' | - ForEach-Object { - $Key, $Value = $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value - Set-Item "Env:$Key" $Value - } - - # ? Environment-specific setup - if ($CI) { dev 'elevate-pyright-warnings' } - elseif ($Devcontainer) { - $Repo = Get-ChildItem '/workspaces' - $Packages = Get-ChildItem "$Repo/packages" - $SafeDirs = @($Repo) + $Packages - foreach ($Dir in $SafeDirs) { - if (!($SafeDirs -contains $Dir)) { - git config --global --add safe.directory $Dir + # ? Set up CI and contributor environments + if ($_CI) { + $GithubPath = Get-Content $Env:GITHUB_PATH + foreach ($Path in @("$PWD/.venv/bin", "$PWD/.venv/scripts")) { + if (!($GithubPath | Select-String -Pattern [regex]::Escape($Path))) { + Add-Content $Env:GITHUB_PATH $Path } } } - - # ? Install pre-commit hooks else { + # ? Install pre-commit hooks $Hooks = '.git/hooks' - if ( - !(Test-Path "$Hooks/pre-commit") -or - !(Test-Path "$Hooks/post-checkout") - ) { uv run --no-sync --python $PythonVersion pre-commit install --install-hooks } + if ( !(Test-Path "$Hooks/pre-commit") -or !(Test-Path "$Hooks/post-checkout") ) { + Invoke-Uv -PythonVersion $PythonVersion 'pre-commit' 'install' '--install-hooks' + } + # ? Install Pylance extension if (!$Devcontainer -and (Get-Command -Name 'code' -ErrorAction 'Ignore')) { $LocalExtensions = '.vscode/extensions' $Pylance = 'ms-python.vscode-pylance' @@ -196,10 +183,47 @@ function Invoke-Uv { } } } + + # ? Sync `.env` and set environment variables from `pyproject.toml` + $EnvVars = dev 'sync-environment-variables' + $EnvVars | Set-Content ($Env:GITHUB_ENV ? $Env:GITHUB_ENV : "$PWD/.env") + $EnvVars | Select-String -Pattern '^(.+?)=(.+)$' | ForEach-Object { + $K, $V = $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value + Set-Item "Env:$K" $V + } + $ProjEnvJson = '{' + (dev 'sync-environment-variables' --config-only) | + Select-String -Pattern '^(.+?)=(.+)$' | + ForEach-Object { + $K, $V = $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value + $ProjEnvJson += "`n `"$K`": `"$V`"," + } + $ProjEnvJson = "$($ProjEnvJson.TrimEnd(','))`n }" + $Settings = '.vscode/settings.json' + $SettingsContent = Get-Content $Settings -Raw + foreach ($Plat in ('linux', 'osx', 'windows')) { + $Pat = "(?m)`"terminal\.integrated\.env\.$Plat`"\s*:\s*\{[^}]*\}" + $Repl = "`"terminal.integrated.env.$Plat`": $ProjEnvJson" + $SettingsContent = $SettingsContent -Replace $Pat, $Repl + } + Set-Content $Settings $SettingsContent -NoNewline + + # ? Environment-specific setup + if ($_CI) { dev 'elevate-pyright-warnings' } + elseif ($Devcontainer) { + $Repo = Get-ChildItem '/workspaces' + $Packages = Get-ChildItem "$Repo/packages" + $SafeDirs = @($Repo) + $Packages + foreach ($Dir in $SafeDirs) { + if (!($SafeDirs -contains $Dir)) { + git config --global --add safe.directory $Dir + } + } + } } } } - Process { if ($Run) { uv run --no-sync --python $PythonVersion $Run } } + Process { if ($Run) { uv run $Run } } } function Invoke-Just { @@ -213,7 +237,7 @@ function Invoke-Just { [switch]$High, [switch]$Build, [switch]$Force, - [switch]$CI, + [switch]$_CI, [switch]$Locked, [switch]$Devcontainer, [string]$PythonVersion = (Get-Content '.python-version'), @@ -221,7 +245,7 @@ function Invoke-Just { [Parameter(ValueFromPipeline, ValueFromRemainingArguments)][string[]]$Run ) Begin { - $CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)) + $_CI = (New-Switch $Env:SYNC_ENV_DISABLE_CI (New-Switch $Env:CI)) $InvokeUvArgs = @{ Sync = $Sync Update = $Update @@ -229,8 +253,8 @@ function Invoke-Just { High = $High Build = $Build Force = $Force - CI = $CI - Locked = $CI + _CI = $_CI + Locked = $_CI Devcontainer = (New-Switch $Env:SYNC_ENV_DISABLE_DEVCONTAINER (New-Switch $Env:DEVCONTAINER)) PythonVersion = $PythonVersion PylanceVersion = $PylanceVersion diff --git a/docs/conf.py b/docs/conf.py index 7bad04b8..1d8cd122 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -162,7 +162,7 @@ def add_version_to_css(app: Sphinx, _pagename, _templatename, ctx, _doctree): project = const.ans.package -copyright = f"{date.today().year}, {const.authors}" # noqa: A001 +copyright = f"{date.today().year}, {const.authors}" version = const.ans.version master_doc = "index" language = "en" diff --git a/packages/_dev/boilercv_dev/__init__.py b/packages/_dev/boilercv_dev/__init__.py index 9d199894..1d1ac9ae 100644 --- a/packages/_dev/boilercv_dev/__init__.py +++ b/packages/_dev/boilercv_dev/__init__.py @@ -4,6 +4,8 @@ from pathlib import Path from shlex import quote +from cappa.base import command + def escape(path: str | Path) -> str: """Escape a path, suitable for passing to e.g. {func}`~subprocess.run`.""" @@ -22,3 +24,11 @@ def log(obj): log(escape(obj)) case _: print(obj) # noqa: T201 + + +@command(invoke="boilercv_dev.tools.environment.sync_environment_variables") +class SyncEnvironmentVariables: + """Sync `.env` with `pyproject.toml`.""" + + config_only: bool = False + """Only get the config environment variables.""" diff --git a/packages/_dev/boilercv_dev/__main__.py b/packages/_dev/boilercv_dev/__main__.py index 458195fb..fb7584c3 100644 --- a/packages/_dev/boilercv_dev/__main__.py +++ b/packages/_dev/boilercv_dev/__main__.py @@ -5,10 +5,7 @@ from cappa.base import command, invoke from cappa.subcommand import Subcommands - -@command(invoke="boilercv_dev.tools.environment.sync_environment_variables") -class SyncEnvironmentVariables: - """Sync `.env` with `pyproject.toml`.""" +from boilercv_dev import SyncEnvironmentVariables @command(invoke="boilercv_dev.tools.add_change") diff --git a/packages/_dev/boilercv_dev/tests/__init__.py b/packages/_dev/boilercv_dev/tests/__init__.py index 89c49e3c..e8b5c14e 100644 --- a/packages/_dev/boilercv_dev/tests/__init__.py +++ b/packages/_dev/boilercv_dev/tests/__init__.py @@ -103,7 +103,7 @@ class Caser: def __call__( self, stage: str, - id: str = "_", # noqa: A002 + id: str = "_", params: dict[str, Any] | None = None, results: list[str] | None = None, marks: Sequence[pytest.Mark] | None = None, diff --git a/packages/_dev/boilercv_dev/tools/environment.py b/packages/_dev/boilercv_dev/tools/environment.py index 269ad269..f01a4c11 100644 --- a/packages/_dev/boilercv_dev/tools/environment.py +++ b/packages/_dev/boilercv_dev/tools/environment.py @@ -15,6 +15,7 @@ ) import boilercv_dev +from boilercv_dev import SyncEnvironmentVariables from boilercv_dev.docs.models.paths import rooted_paths from boilercv_dev.modules import get_module_name @@ -45,13 +46,14 @@ class Constants(BaseModel): const = Constants() -def sync_environment_variables( - pylance_version: str = const.pylance_version, setenv: bool = True -): +def sync_environment_variables(args: SyncEnvironmentVariables): """Sync `.env` with `pyproject.toml`, optionally setting environment variables.""" config_env = Config().env - if pylance_version: - config_env["PYRIGHT_PYTHON_PYLANCE_VERSION"] = pylance_version + if const.pylance_version: + config_env["PYRIGHT_PYTHON_PYLANCE_VERSION"] = const.pylance_version + config_env = dict(sorted(config_env.items())) + if args.config_only: + return print("\n".join(f"{k}={v}" for k, v in config_env.items())) # noqa: T201 dotenv = dotenv_values(const.env) keys_set: list[str] = [] for key in dotenv: @@ -61,11 +63,9 @@ def sync_environment_variables( for k, v in config_env.items(): if k not in keys_set: dotenv[k] = v - if setenv: - load_dotenv(stream=StringIO("\n".join(f"{k}={v}" for k, v in dotenv.items()))) - const.env.write_text( - encoding="utf-8", data="\n".join(f"{k}={v}" for k, v in dotenv.items()) - ) + formatted_dotenv = "\n".join(f"{k}={v}" for k, v in dotenv.items()) + load_dotenv(stream=StringIO(formatted_dotenv)) + print(formatted_dotenv) # noqa: T201 def run(*args: str, check: bool = True, **kwds): diff --git a/packages/pipeline/boilercv_pipeline/parser.py b/packages/pipeline/boilercv_pipeline/parser.py index c53c251b..eb8db560 100644 --- a/packages/pipeline/boilercv_pipeline/parser.py +++ b/packages/pipeline/boilercv_pipeline/parser.py @@ -32,7 +32,7 @@ def invoke( backend: Callable[..., Any] | None = None, color: bool = True, version: str | Arg[Any] | None = None, - help: bool | Arg[Any] = True, # noqa: A002 + help: bool | Arg[Any] = True, completion: bool | Arg[Any] = True, theme: Any | None = None, output: Output | None = None, diff --git a/pyproject.toml b/pyproject.toml index 12e43fc6..1f8dd889 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ dev = [ "pipx>=1.6.0", "pre-commit>=4.0.1", "pyright>=1.1.371", - "ruff>=0.5.1", + "ruff>=0.9.3", "rust-just>=1.36.0", "snakeviz>=2.2.0", "sourcery>=1.21.0", @@ -124,7 +124,7 @@ context-models = { path = "packages/context_models", editable = true } branch = true source = ["boilercv_dev", "boilercv_pipeline", "boilercv", "tests"] -[tool.dev.env] +[tool.boilercv_dev.env] COVERAGE_CORE = "sysmon" JUPYTER_PLATFORM_DIRS = "1" PYDEVD_DISABLE_FILE_VALIDATION = "1" @@ -325,6 +325,16 @@ extend-safe-fixes = [ "F401", # Allow autofix for unused imports even in `__init__.py` ] ignore = [ + "A0", # TODO: Decide whether to unsuppress and fix + "B909", # TODO: Decide whether to unsuppress and fix + "BLE001", # TODO: Decide whether to unsuppress and fix + "DOC2", # TODO: Decide whether to unsuppress and fix + "DOC4", # TODO: Decide whether to unsuppress and fix + "DOC5", # TODO: Decide whether to unsuppress and fix + "LOG015", # TODO: Decide whether to unsuppress and fix + "PLE1206", # TODO: Decide whether to unsuppress and fix + "RUF039", # TODO: Decide whether to unsuppress and fix + "RUF052", # TODO: Decide whether to unsuppress and fix "ANN", # Don't require type annotations "ARG005", # Allow unused lambda argument. For consistency across df pipelines. "C408", # Allow dict calls @@ -369,7 +379,7 @@ ignore = [ "S301", # Don't warn about pickling. "S403", # Don't warn about pickle-like modules. "S404", # Don't warn about subprocess. - "TCH", # Type checking linter doesn't play nicely with pydantic + "TC", # Type checking linter doesn't play nicely with pydantic "TD", # Disable to-do validation. Too pedantic for now. "TRY003", # Allow long exception messages "W2", # Allow whitespace issues. Fixed automatically by black. diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index d0deaaae..a8b7c29f 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -267,7 +267,7 @@ rpds-py==0.22.3 rsa==4.9 ruamel-yaml==0.18.6 ruamel-yaml-clib==0.2.12 ; python_full_version < '3.13' and platform_python_implementation == 'CPython' -ruff==0.5.1 +ruff==0.9.3 rust-just==1.36.0 scikit-image==0.23.1 scipy==1.11.2 diff --git a/tests/conftest.py b/tests/conftest.py index b01f0528..65da1488 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -254,9 +254,9 @@ def pytest_harvest_xdist_worker_dump(worker_id, session_items, fixture_store): with (RESULTS_PATH / f"{worker_id}.pkl").open("wb") as f: try: pickle.dump((session_items, fixture_store), f) - except Exception as e: # noqa: BLE001 - warning( # noqa: PLE1206 - "Error while pickling worker %s's harvested results: " "[%s] %s", + except Exception as e: + warning( + "Error while pickling worker %s's harvested results: [%s] %s", (worker_id, e.__class__, e), ) return True diff --git a/uv.lock b/uv.lock index 4888a454..262c0f7d 100644 --- a/uv.lock +++ b/uv.lock @@ -624,7 +624,7 @@ all = [ { name = "pytest-github-actions-annotate-failures", specifier = ">=0.2.0" }, { name = "pytest-plt", specifier = ">=1.1.1" }, { name = "pytest-xdist", extras = ["psutil", "setproctitle"], specifier = ">=3.6.1" }, - { name = "ruff", specifier = ">=0.5.1" }, + { name = "ruff", specifier = ">=0.9.3" }, { name = "rust-just", specifier = ">=1.36.0" }, { name = "snakeviz", specifier = ">=2.2.0" }, { name = "sourcery", specifier = ">=1.21.0" }, @@ -651,7 +651,7 @@ dev = [ { name = "pre-commit", specifier = ">=4.0.1" }, { name = "pycine", git = "https://github.com/ottomatic-io/pycine?rev=815cfca06cafc50745a43b2cd0168982225c6dca#815cfca06cafc50745a43b2cd0168982225c6dca" }, { name = "pyright", specifier = ">=1.1.371" }, - { name = "ruff", specifier = ">=0.5.1" }, + { name = "ruff", specifier = ">=0.9.3" }, { name = "rust-just", specifier = ">=1.36.0" }, { name = "snakeviz", specifier = ">=2.2.0" }, { name = "sourcery", specifier = ">=1.21.0" }, @@ -4866,27 +4866,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/dc/8d95ce5e15f0f25dc1fb6e9a11d6f33379e092d528a3b0535de246e07182/ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655", size = 2594019 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/36/c4684f20bc0f6e4725177fbed8557a1d4c8dd118584112313ee03876f4dd/ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6", size = 9506184 }, - { url = "https://files.pythonhosted.org/packages/40/98/80295e661ba1219c584a2d6103277bce16c6ff7cd0d9e3597bb16c115113/ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c", size = 8606624 }, - { url = "https://files.pythonhosted.org/packages/a9/64/b0356632574dea983e2d718f064d95f8a45f8f381d094c917685f1d6fc26/ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d", size = 8184772 }, - { url = "https://files.pythonhosted.org/packages/34/57/db0df86298aa6082c396b44d4ad12a16ee891f61513849e5bdaeab0a4c7a/ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa", size = 9981131 }, - { url = "https://files.pythonhosted.org/packages/bb/b1/63211390db6afa0e24bdd5b1d5053693074759ce940c0669576834fc6026/ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994", size = 9297773 }, - { url = "https://files.pythonhosted.org/packages/ac/7f/5824713ffcb5ce055dc7e509cb2d5d5ef405af73cd1615d27e475114dc89/ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0", size = 10087567 }, - { url = "https://files.pythonhosted.org/packages/75/3b/1ada2a113e1899e215e7542a42e59e5143d7ce4e9b04b7f9f03217414139/ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d", size = 10863472 }, - { url = "https://files.pythonhosted.org/packages/b2/f9/11ca13d8040830140d4385af241d435e1b1b1387a987df707c969c5bbfb9/ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78", size = 10448311 }, - { url = "https://files.pythonhosted.org/packages/b7/43/8546df86010041ab9f7e80f1a85cd4b8042a55338d7a30561ef835cba9e2/ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96", size = 11368918 }, - { url = "https://files.pythonhosted.org/packages/8a/d5/8271d42dd239b7c2d163615b3b01b1acfb187f5114bfca6d5a85e1d6a1eb/ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1", size = 10144007 }, - { url = "https://files.pythonhosted.org/packages/39/38/c480773c22012535ca121c9488323943406c1780f22f9bb5ca51e830c2b1/ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929", size = 9940919 }, - { url = "https://files.pythonhosted.org/packages/5f/ea/6d96bd900cfe2a2401de733c4bd9ee3e5811cb27584ade3bbcdee638afc8/ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3", size = 9361679 }, - { url = "https://files.pythonhosted.org/packages/7d/7c/b4ab2d5d90bab6e16ea79c261aeb437cb804b436d376c70f37f37086907c/ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108", size = 9717533 }, - { url = "https://files.pythonhosted.org/packages/6d/a3/fb5a4ee18cee7b44e9309a9b8d7b8d76a12ee7f3ef53f6c0dcc71c080f2d/ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c", size = 10204312 }, - { url = "https://files.pythonhosted.org/packages/9c/39/da2a5cc52fd239e291c0ff473137d6733778e481b8e4469fd1c4c891c7f7/ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4", size = 7775009 }, - { url = "https://files.pythonhosted.org/packages/a8/9f/e236acf3b95b383a5241da0f758fc8688d1796837b6bec8ee528130c3dba/ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d", size = 8597783 }, - { url = "https://files.pythonhosted.org/packages/f6/b1/fd215876543ac2a3ddd477487f574ca2d91973d0ecd87664095e6b249017/ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2", size = 7998017 }, +version = "0.9.3" +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 } +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 }, ] [[package]]