├── .github └── workflows │ ├── adopt-ruff.yml │ └── pre-commit.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── launch.json ├── README.md ├── action.yml ├── adopt_ruff ├── __init__.py ├── main.py ├── models │ ├── __init__.py │ ├── ruff_config.py │ ├── ruff_output.py │ └── rule.py └── utils.py ├── pyproject.toml └── uv.lock /.github/workflows/adopt-ruff.yml: -------------------------------------------------------------------------------- 1 | name: Adopt Ruff 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | adopt-ruff: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Check out repository code 16 | uses: actions/checkout@v4 17 | 18 | - name: Install ruff 19 | run: pip install ruff 20 | shell: bash 21 | 22 | - name: Set up python 23 | id: setup-python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.11" 27 | 28 | - name: Run the adopt-ruff action 29 | uses: ./ 30 | with: 31 | ref: ${{ github.sha }} 32 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: tox-dev/action-pre-commit-uv@v1 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ruff_cache 2 | __pycache__ 3 | /rules.json 4 | /violations.json 5 | /result.md 6 | /artifacts 7 | .python-version 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.8.4 4 | hooks: 5 | - id: ruff 6 | args: [--fix] 7 | - id: ruff-format 8 | - repo: https://github.com/pre-commit/mirrors-mypy 9 | rev: v1.14.0 10 | hooks: 11 | - id: mypy 12 | additional_dependencies: 13 | - types-tabulate 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v5.0.0 16 | hooks: 17 | - id: trailing-whitespace 18 | - id: end-of-file-fixer 19 | - id: check-toml 20 | - id: check-yaml 21 | - id: check-json 22 | - id: check-ast 23 | - id: check-merge-conflict 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python: Current File", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "cwd": "${workspaceFolder}", 10 | "console": "integratedTerminal", 11 | "justMyCode": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adopt-Ruff 2 | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) 3 | 4 | Adopt [Ruff⚡](https://ruff.rs) in your repo faster 😎 5 | 6 | A tool for finding Ruff rules that are not yet configured, and can be added to your repository easily: 7 | 8 | - Rules your code already follows 👏 9 | - Rules that can be automatically fixed by Ruff 🪄 10 | - Rules violated in the repository, sorted by ascending violation count 🔎 11 | 12 | The output is a markdown report, easy to check as a Github action summary and CSV files with relevant Rule information per category. 13 | 14 | _See example at the bottom of this page_ 15 | 16 | 17 | 18 | ## Configurations 19 | To decide whether to consider a rule, `adopt-ruff` uses arguments. See below on how to configure them. 20 | 21 | - Ruff considers some new or experimental rules `in preview`. These should be used with caution. You can choose whether `adopt-ruff` considers them or not. Read more about Ruff's preview [here](https://docs.astral.sh/ruff/preview/). 22 | - Ruff supports many autofixable rules - some are always autofixable, and some can only be autofixed by Ruff in specific cases. It's up to you whether to consider "sometimes-autofixable" as autofixable. 23 | 24 | ## Usage 25 | 26 | ### As a Github action (recommended) 27 | Create a yaml file under `.github/workspaces` in your repo, with the following content: 28 | 29 | ```yaml 30 | name: Adopt Ruff 31 | on: workflow_dispatch 32 | 33 | jobs: 34 | adopt-ruff: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Check out repository code 38 | uses: actions/checkout@v4 39 | 40 | - name: Set up python 41 | id: setup-python 42 | uses: actions/setup-python@v5 43 | with: 44 | python-version: 3.x 45 | 46 | - name: Install ruff 47 | run: pip install ruff 48 | 49 | - name: Run the adopt-ruff action 50 | uses: ScDor/adopt-ruff@master 51 | 52 | ``` 53 | Now, manually trigger the workflow from the `Actions` panel of your repository.\ 54 | _Learn more about manually triggering a workflow [here](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow)._ 55 | 56 | When the action is done running, head to the [job summary](https://github.blog/wp-content/uploads/2022/05/newjobsummary.png) to watch the report. 57 | 58 | The output tables are also availalbe as CSV files, under `Artifacts`. 59 | 60 | Re-run this action to get insights about applicable rules when Ruff updates with more exciting rules ⚡ 61 | 62 | #### Notes: 63 | - This flow always installs Ruff's latest version. If your project already has Ruff as a dependency (i.e. in `pyproject.toml` or `requirements.txt`), replace the `Install ruff` step with an appropriate command. 64 | 65 | - adopt-ruff calls Ruff, assuming it's installed in the environment. adopt-ruff will fail if ruff is not found. 66 | 67 | 68 | #### Arguments 69 | To pass arguments different than the default, use the `with` key. The values mentioned below are the default. Omit any argument to use the default. 70 | 71 | ```yaml 72 | - name: Run the adopt-ruff action 73 | uses: ScDor/adopt-ruff@master 74 | with: 75 | path: "." 76 | repo-name: "" # won't show in report if blank 77 | config-file-path: "" # will be searched in `path` if empty 78 | include-preview: True 79 | include-sometimes-fixable: False 80 | ``` 81 | 82 | ### Locally 83 | Install by running `pip install git+https://github.com/ScDor/adopt-ruff`.\ 84 | Run `adopt-ruff --help` for more information.\ 85 | 86 | 87 | #### Arguments 88 | - `--path`: directory adopt-ruff will search for ruff violations. 89 | - `--ruff-conf-path`: Path to the `pyproject.toml` ,`ruff.toml` or `.ruff.toml`. When not provided, `adopt-ruff` will attempt to seach one of those under `path`. 90 | - `--sometimes-fixable`: whether to consider sometimes-fixable rules as fixable. 91 | - `--preview`/`--no-preview`: whether to include preview rules. 92 | - `--repo-name`: Will be shown in the output. When not provided, won't be shown. 93 | 94 | 95 | # Report Example 96 | ## adopt-ruff report for ScDor/my-dummy-repo (ruff 0.1.13) 97 | 98 | ## Respected Ruff rules 99 | 100 | 374 Ruff rules are already respected in the repo - they can be added right away 🚀 101 |
102 | Details 103 | 104 | | Code | Name | Fixable | Preview | Linter | 105 | |----------|----------------------------------------------|-----------|-----------|----------------------------| 106 | | A003 | builtin-attribute-shadowing | No | False | flake8-builtins | 107 | | AIR001 | airflow-variable-name-task-id-mismatch | No | False | Airflow | 108 | | ASYNC100 | blocking-http-call-in-async-function | No | False | flake8-async | 109 | | ASYNC101 | open-sleep-or-subprocess-in-async-function | No | False | Perflint | 110 | | PERF403 | manual-dict-comprehension | No | True | Perflint | 111 | 112 | (table truncated for example purposes) 113 | 114 |
115 | 116 | ## Autofixable Ruff rules 117 | 118 | 65 Ruff rules are violated in the repo, but can be auto-fixed 🪄 119 |
120 | Details 121 | 122 | | Code | Name | Fixable | Preview | Linter | 123 | |---------|---------------------------------------------------|-----------|-----------|-----------------------| 124 | | B010 | set-attr-with-constant | Always | False | flake8-bugbear | 125 | | B011 | assert-false | Always | False | flake8-bugbear | 126 | | C401 | unnecessary-generator-set | Always | False | flake8-comprehensions | 127 | 128 | (table truncated for example purposes) 129 | 130 |
131 | 132 | ## Applicable Rules 133 | 134 | 194 other Ruff rules are not yet configured in the repository 135 |
136 | Details 137 | 138 | | Code | Name | Fixable | Preview | Linter | Violations | 139 | |---------|--------------------------------------------------|-----------|-----------|----------------------------|--------------| 140 | | PLW0127 | self-assigning-variable | No | False | Pylint | 1 | 141 | | RUF009 | function-call-in-dataclass-default-argument | No | False | Ruff-specific rules | 1 | 142 | | S314 | suspicious-xml-element-tree-usage | No | False | flake8-bandit | 1 | 143 | | B005 | strip-with-multi-characters | No | False | flake8-bugbear | 1 | 144 | | PTH116 | os-stat | No | False | flake8-use-pathlib | 6 | 145 | | N803 | invalid-argument-name | No | False | pep8-naming | 6 | 146 | | UP032 | f-string | Sometimes | False | pyupgrade | 6 | 147 | | B019 | cached-instance-method | No | False | flake8-bugbear | 7 | 148 | | B017 | assert-raises-exception | No | False | flake8-bugbear | 8 | 149 | | TCH002 | typing-only-third-party-import | Sometimes | False | flake8-type-checking | 8 | 150 | | PLW1508 | invalid-envvar-default | No | False | Pylint | 9 | 151 | | S607 | start-process-with-partial-path | No | False | flake8-bandit | 9 | 152 | | DTZ007 | call-datetime-strptime-without-zone | No | False | flake8-datetimez | 10 | 153 | | D205 | blank-line-after-summary | Sometimes | False | pydocstyle | 2860 | 154 | 155 | (table truncated for example purposes) 156 | 157 |
158 | 159 | | Configuration | Value | 160 | |---------------------------------|---------| 161 | | Include sometimes-fixable rules | False | 162 | | Include preview rules | True | 163 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "adopt-ruff" 2 | branding: 3 | icon: "arrow-up" 4 | color: "yellow" 5 | 6 | description: "Adopt ruff easily" 7 | inputs: 8 | ref: 9 | description: "adopt-ruff reference to install" 10 | default: master 11 | path: 12 | description: "directory adopt-ruff will search for ruff violations. Default is CWD." 13 | default: "." 14 | config-file-path: 15 | description: "Path to the pyproject.toml/ruff.toml file. If not provided, adopt-ruff will attempt to locate it under cwd" 16 | default: "" 17 | repo-name: 18 | description: "name of the repository being checked" 19 | default: ${{ github.repository }} 20 | include-sometimes-fixable: 21 | description: "whether to count sometimes-fixable rules as fixable" 22 | default: "False" 23 | include-preview: 24 | description: "whether to count preview rules" 25 | default: "True" 26 | 27 | runs: 28 | using: "composite" 29 | steps: 30 | - name: Install adopt-ruff 31 | run: pip install git+https://github.com/ScDor/adopt-ruff@${{inputs.ref}} -q 32 | shell: bash 33 | 34 | - name: Run adopt-ruff 35 | run: | 36 | adopt-ruff 37 | cat result.md > $GITHUB_STEP_SUMMARY 38 | env: 39 | ADOPT_RUFF_REPO_NAME: ${{ inputs.repo-name }} 40 | ADOPT_RUFF_SOMETIMES_FIXABLE: ${{ inputs.include-sometimes-fixable }} 41 | ADOPT_RUFF_PREVIEW: ${{ inputs.include-preview }} 42 | ADOPT_RUFF_CODE_PATH: ${{ inputs.path }} 43 | ADOPT_RUFF_CONFIG_FILE_PATH: ${{ inputs.config-file-path }} 44 | shell: bash 45 | 46 | - uses: actions/upload-artifact@v4 47 | with: 48 | path: artifacts 49 | -------------------------------------------------------------------------------- /adopt_ruff/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScDor/adopt-ruff/59474307bc4c44fda046cfbbc35c14076c7a7fd3/adopt_ruff/__init__.py -------------------------------------------------------------------------------- /adopt_ruff/main.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import json 3 | import subprocess 4 | import sys 5 | from collections.abc import Iterable 6 | from pathlib import Path 7 | from typing import Annotated, Optional 8 | 9 | import more_itertools 10 | import typer 11 | from mdutils.mdutils import MdUtils 12 | from packaging.version import Version 13 | from tabulate import tabulate 14 | 15 | from adopt_ruff.models.ruff_config import RuffConfig 16 | from adopt_ruff.models.ruff_output import Violation 17 | from adopt_ruff.models.rule import FixAvailability, Rule 18 | from adopt_ruff.utils import ARTIFACTS_PATH, logger, output_table, search_config_file 19 | 20 | 21 | def run_ruff(path: Path) -> tuple[set[Rule], tuple[Violation, ...], Version]: 22 | try: 23 | ruff_version = Version( 24 | subprocess.run( 25 | ["ruff", "--version"], 26 | check=True, 27 | text=True, 28 | capture_output=True, 29 | ).stdout.split()[1] # ruff's output is `ruff x.y.z` 30 | ) 31 | logger.debug(f"parsed {ruff_version=!s}") 32 | 33 | except FileNotFoundError: 34 | logger.error("Make sure ruff is installed (pip install ruff)") 35 | sys.exit(1) 36 | 37 | # Now when ruff is found, assume the following commands will run properly 38 | rules = { 39 | Rule(**value) 40 | for value in json.loads( 41 | subprocess.run( 42 | ["ruff", "rule", "--all", "--output-format=json"], 43 | check=True, 44 | text=True, 45 | capture_output=True, 46 | ).stdout 47 | ) 48 | } 49 | logger.debug(f"read {len(rules)} rules from JSON output") 50 | 51 | violations = tuple( 52 | Violation(**value) 53 | for value in json.loads( 54 | subprocess.run( 55 | [ 56 | *["ruff" if ruff_version < Version("0.3.0") else "ruff", "check"], 57 | str(path), 58 | "--output-format=json", 59 | "--select=ALL", 60 | "--exit-zero", 61 | ], 62 | check=True, 63 | text=True, 64 | capture_output=True, 65 | ).stdout 66 | ) 67 | ) 68 | logger.debug(f"read {len(violations)} violations from JSON output") 69 | return rules, violations, ruff_version 70 | 71 | 72 | def run( 73 | rules: set[Rule], 74 | violations: tuple[Violation, ...], 75 | config: RuffConfig, 76 | ruff_version: Version, 77 | include_sometimes_fixable: bool, 78 | include_preview: bool, 79 | repo_name: str | None = None, 80 | ) -> str: 81 | md = MdUtils("output") 82 | 83 | repo_name_header = f"for {repo_name} " if repo_name else "" 84 | md.new_header(1, f"adopt-ruff report {repo_name_header}(ruff {ruff_version!s})") 85 | 86 | configured_rules = config.all_rules 87 | 88 | if respected := sorted( 89 | respected_rules( 90 | violations, 91 | rules, 92 | configured_rules, 93 | include_preview, 94 | ), 95 | key=lambda rule: rule.code, 96 | ): 97 | md.new_header(2, "Respected Ruff rules") 98 | md.new_line( 99 | f"{len(respected)} Ruff rules are already respected in the repo - " 100 | "they can be added right away 🚀" 101 | ) 102 | output_table( 103 | items=([r.as_dict() for r in respected]), 104 | path=ARTIFACTS_PATH / "respected.csv", 105 | md=md, 106 | collapsible=True, 107 | ) 108 | 109 | if autofixable := sort_by_code( 110 | autofixable_rules( 111 | violations, 112 | rules, 113 | configured_rules, 114 | include_sometimes_fixable, 115 | include_preview, 116 | ) 117 | ): 118 | md.new_header(2, "Autofixable Ruff rules") 119 | always_status = " (sometimes)" if include_sometimes_fixable else "" 120 | md.new_line( 121 | f"{len(autofixable)} Ruff rules are violated in the repo, but can{always_status} be auto-fixed 🪄" 122 | ) 123 | output_table( 124 | items=([r.as_dict() for r in autofixable]), 125 | path=ARTIFACTS_PATH / "autofixable.csv", 126 | md=md, 127 | collapsible=True, 128 | ) 129 | 130 | if violated_rule_to_violations := ( 131 | violated_rules( 132 | violations, 133 | rules, 134 | excluded_rules=( 135 | itertools.chain.from_iterable( 136 | (configured_rules, respected, autofixable) 137 | ) 138 | ), 139 | include_preview=include_preview, 140 | ) 141 | ): 142 | rule_to_violation_count = { 143 | rule: len(violations_) 144 | for rule, violations_ in violated_rule_to_violations.items() 145 | } 146 | 147 | applicable_rules = sorted( 148 | violated_rule_to_violations.keys(), 149 | key=lambda rule: (rule_to_violation_count[rule], rule.linter, rule.code), 150 | ) 151 | 152 | md.new_header(2, "Applicable Rules") 153 | md.new_line( 154 | f"{len(applicable_rules)} other Ruff rules are not yet configured in the repository" 155 | ) 156 | output_table( 157 | items=( 158 | [ 159 | r.as_dict() | {"Violations": rule_to_violation_count[r]} 160 | for r in applicable_rules 161 | ] 162 | ), 163 | path=ARTIFACTS_PATH / "applicable.csv", 164 | md=md, 165 | collapsible=True, 166 | ) 167 | 168 | if not any((respected, autofixable, violated_rule_to_violations)): 169 | md.new_line( 170 | f"You adopted Ruff well! 👏 {len(rules)} ruff rules are either selected or ignored." 171 | ) 172 | if not include_preview: 173 | md.new_line( 174 | "You used --no-preview, ignoring rules in preview-mode.\n" 175 | "Consider running adopt-ruff again with the `--preview` flag: there may be more useful rules there 🔍\n" 176 | "Visit ⚡[Ruff's docs](https://docs.astral.sh/ruff/faq/#what-is-preview) for more information." 177 | ) 178 | elif not include_sometimes_fixable: 179 | md.new_line( 180 | "Consider running adopt-ruff again with the `--sometimes-fixable flag`" 181 | ) 182 | 183 | md.new_line( 184 | tabulate( 185 | [ 186 | ["Include sometimes-fixable rules", include_sometimes_fixable], 187 | ["Include preview rules", include_preview], 188 | ], 189 | tablefmt="github", 190 | headers=["Configuration", "Value"], 191 | ) 192 | ) 193 | return md.get_md_text() 194 | 195 | 196 | def sort_by_code(rules: Iterable[Rule]) -> list[Rule]: 197 | return sorted(rules, key=lambda rule: rule.code) 198 | 199 | 200 | def respected_rules( 201 | violations: tuple[Violation, ...], 202 | rules: Iterable[Rule], 203 | configured_rules: set[Rule], 204 | include_preview: bool, 205 | ) -> set[Rule]: 206 | violated_codes = {v.code for v in violations} 207 | 208 | return { 209 | rule 210 | for rule in rules 211 | if (rule.code not in violated_codes) 212 | and (rule not in configured_rules) 213 | and (include_preview or (not rule.preview)) 214 | } 215 | 216 | 217 | def autofixable_rules( 218 | violations: tuple[Violation, ...], 219 | rules: Iterable[Rule], 220 | configured_rules: set[Rule], 221 | include_sometimes_fixable: bool = False, 222 | include_preview: bool = False, 223 | ) -> set[Rule]: 224 | violated_codes = {v.code for v in violations} 225 | 226 | return { 227 | rule 228 | for rule in rules 229 | if rule.code in violated_codes 230 | and rule not in configured_rules 231 | and rule.is_fixable 232 | and (not rule.preview if not include_preview else True) 233 | and ( 234 | rule.fix == FixAvailability.ALWAYS 235 | if not include_sometimes_fixable 236 | else True 237 | ) 238 | } 239 | 240 | 241 | def violated_rules( 242 | violations: tuple[Violation, ...], 243 | rules: set[Rule], 244 | excluded_rules: Iterable[Rule], 245 | include_preview: bool, 246 | ) -> dict[Rule, tuple[Violation, ...]]: 247 | ignore_codes = {rule.code for rule in excluded_rules} 248 | return { 249 | rule: violations 250 | for rule, violations in map_rules_to_violations(rules, violations).items() 251 | if (rule.code not in ignore_codes) and (include_preview or (not rule.preview)) 252 | } 253 | 254 | 255 | def map_rules_to_violations( 256 | rules: Iterable[Rule], 257 | violations: Iterable[Violation], 258 | ) -> dict[Rule, tuple[Violation, ...]]: 259 | code_to_violations = more_itertools.bucket(violations, key=lambda v: v.code) 260 | code_to_rule = {rule.code: rule for rule in rules} 261 | 262 | return { 263 | code_to_rule[code]: tuple(code_to_violations[code]) 264 | for code in code_to_violations 265 | } 266 | 267 | 268 | def _main( 269 | code_path: Annotated[ 270 | Path, 271 | typer.Option( 272 | help="The directory on which ruff should be run. If not provided, the current directory will be used.", 273 | envvar="ADOPT_RUFF_CODE_PATH", 274 | exists=True, 275 | dir_okay=True, 276 | file_okay=False, 277 | ), 278 | ] = Path(), 279 | ruff_conf_path: Annotated[ 280 | Optional[Path], # noqa: UP007 281 | typer.Option( 282 | help="Path to the pyproject.toml/ruff.toml file. If not provided, adopt-ruff will attempt to locate it.", 283 | envvar="ADOPT_RUFF_CONFIG_FILE_PATH", 284 | exists=True, 285 | dir_okay=False, 286 | ), 287 | ] = None, 288 | include_sometimes_fixable: Annotated[ 289 | bool, 290 | typer.Option( 291 | "--sometimes-fixable", 292 | help="consider sometimes-fixable rules as fixable", 293 | envvar="ADOPT_RUFF_SOMETIMES_FIXABLE", 294 | is_flag=True, 295 | rich_help_panel="Rule configurations", 296 | ), 297 | ] = False, 298 | include_preview: Annotated[ 299 | bool, 300 | typer.Option( 301 | "--preview/--no-preview", 302 | help="include preview rules. See https://docs.astral.sh/ruff/faq/#what-is-preview", 303 | envvar="ADOPT_RUFF_PREVIEW", 304 | is_flag=True, 305 | rich_help_panel="Rule configurations", 306 | ), 307 | ] = True, 308 | repo_name: Annotated[ 309 | Optional[str], # noqa: UP007 310 | typer.Option( 311 | help="The repository name for the report", 312 | envvar="ADOPT_RUFF_REPO_NAME", 313 | ), 314 | ] = None, 315 | ): 316 | logger.debug(f"{code_path.resolve()=!s}") 317 | logger.debug(f"{ruff_conf_path=!s}") 318 | logger.debug(f"{include_preview=}") 319 | logger.debug(f"{include_sometimes_fixable=}") 320 | logger.debug(f"{repo_name=}") 321 | 322 | rules, violations, ruff_version = run_ruff(code_path) 323 | config: RuffConfig = RuffConfig.from_file( 324 | path=ruff_conf_path or search_config_file(code_path), 325 | rules=rules, 326 | ) 327 | 328 | result = run( 329 | rules=rules, 330 | violations=violations, 331 | config=config, 332 | ruff_version=ruff_version, 333 | include_preview=include_preview, 334 | include_sometimes_fixable=include_sometimes_fixable, 335 | repo_name=repo_name, 336 | ) 337 | 338 | Path("result.md").write_text(result) 339 | logger.debug("wrote output to result.md") 340 | 341 | 342 | def main(): 343 | typer.run(_main) 344 | 345 | 346 | if __name__ == "__main__": 347 | main() 348 | -------------------------------------------------------------------------------- /adopt_ruff/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScDor/adopt-ruff/59474307bc4c44fda046cfbbc35c14076c7a7fd3/adopt_ruff/models/__init__.py -------------------------------------------------------------------------------- /adopt_ruff/models/ruff_config.py: -------------------------------------------------------------------------------- 1 | import tomllib 2 | from pathlib import Path 3 | 4 | from pydantic import BaseModel 5 | 6 | from adopt_ruff.models.rule import Rule 7 | from adopt_ruff.utils import logger 8 | 9 | MIN_RULE_CODE_LEN = 4 10 | DEFAULT_SELECT_RULES = ("E", "F") 11 | 12 | 13 | class RawRuffConfig(BaseModel): 14 | selected_codes: set[str] 15 | ignored_codes: set[str] 16 | 17 | @classmethod 18 | def read_toml(cls, path: Path) -> "RawRuffConfig": 19 | try: 20 | toml = tomllib.loads(path.read_text()) 21 | except ValueError as e: 22 | raise ValueError(f"make sure that {path.name} is a valid toml") from e 23 | 24 | match path.name: 25 | case "pyproject.toml": 26 | if (ruff_section := toml.get("tool", {}).get("ruff")) is None: 27 | logger.warning( 28 | f"could not find `tool` or `tool.ruff` in {path.name}, using default" 29 | ) 30 | return cls.default_config() 31 | 32 | case "ruff.toml" | ".ruff.toml": 33 | ruff_section = toml 34 | case _: 35 | raise ValueError( 36 | f"config file must be pyproject.toml, ruff.toml or .ruff.toml, got {path.name}" 37 | ) 38 | 39 | ruff_lint_section = ruff_section.get( 40 | "lint", ruff_section 41 | ) # both are valid, `lint` was added at some point 42 | 43 | raw_select_codes = set(ruff_lint_section.get("select", ())) 44 | raw_ignored_codes = set(ruff_lint_section.get("ignore", ())) 45 | 46 | logger.debug(f"{len(raw_select_codes)=}, {len(raw_ignored_codes)=}") 47 | return RawRuffConfig( 48 | selected_codes=raw_select_codes, 49 | ignored_codes=raw_ignored_codes, 50 | ) 51 | 52 | @classmethod 53 | def default_config(cls) -> "RawRuffConfig": 54 | return RawRuffConfig( 55 | selected_codes=set(DEFAULT_SELECT_RULES), 56 | ignored_codes=set(), 57 | ) 58 | 59 | 60 | class RuffConfig(BaseModel): 61 | selected_rules: set[Rule] 62 | ignored_rules: set[Rule] 63 | 64 | @staticmethod 65 | def from_file(path: Path | None, rules: set[Rule]) -> "RuffConfig": 66 | if path: 67 | logger.debug(f"reading ruff config file from {path!s}") 68 | raw_config = RawRuffConfig.read_toml(path) 69 | else: 70 | logger.warning( 71 | f"Config path was not specified, using default={DEFAULT_SELECT_RULES}" 72 | ) 73 | raw_config = RawRuffConfig.default_config() 74 | 75 | return RuffConfig( 76 | selected_rules=_parse_raw_rules(raw_config.selected_codes, rules), 77 | ignored_rules=_parse_raw_rules(raw_config.ignored_codes, rules), 78 | ) 79 | 80 | @property 81 | def all_rules(self) -> set[Rule]: 82 | return self.selected_rules | self.ignored_rules 83 | 84 | 85 | def _parse_raw_rules(raw_codes: set[str], rules: set[Rule]) -> set[Rule]: 86 | """ 87 | Convert code values (E401), categories (E) and ALL, into Rule objects 88 | """ 89 | if "ALL" in raw_codes: 90 | return rules 91 | 92 | code_to_rule = {rule.code: rule for rule in rules} 93 | 94 | result: set[Rule] = set() 95 | 96 | for code in raw_codes: 97 | if code.isalpha() or len(code) < MIN_RULE_CODE_LEN: 98 | code_rules = tuple( 99 | rule for rule in rules if rule.code.removeprefix(code).isnumeric() 100 | ) 101 | logger.debug( 102 | f"assuming {code} is a category, adding {len(code_rules)} rules: {sorted(r.code for r in code_rules)!s}" 103 | ) 104 | result.update(code_rules) 105 | # TODO are there cases of category names with len>=MIN_RULE_CODE_LEN, mixing alpha&digits? 106 | else: 107 | result.add(code_to_rule[code]) 108 | 109 | logger.debug(f"converted {len(raw_codes)} raw codes into {len(result)} rules") 110 | 111 | return result 112 | -------------------------------------------------------------------------------- /adopt_ruff/models/ruff_output.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Location(BaseModel): 7 | column: int 8 | row: int 9 | 10 | 11 | class Edit(BaseModel): 12 | content: str 13 | end_location: Location 14 | location: Location 15 | 16 | 17 | class Fix(BaseModel): 18 | applicability: str 19 | edits: tuple[Edit, ...] 20 | message: str | None 21 | 22 | 23 | class Violation(BaseModel): 24 | cell: None # TODO handle notebook output 25 | code: str 26 | end_location: Location 27 | filename: str 28 | fix: Fix | None 29 | location: Location 30 | message: str 31 | noqa_row: int 32 | url: str 33 | -------------------------------------------------------------------------------- /adopt_ruff/models/rule.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import Any, Literal 5 | 6 | from pydantic import BaseModel 7 | 8 | 9 | class FixAvailability(Enum): 10 | ALWAYS = "Fix is always available." 11 | SOMETIMES = "Fix is sometimes available." 12 | NOT = "Fix is not available." 13 | 14 | @property 15 | def one_word(self) -> str: 16 | match self: 17 | case FixAvailability.ALWAYS: 18 | return "Always" 19 | case FixAvailability.SOMETIMES: 20 | return "Sometimes" 21 | case FixAvailability.NOT: 22 | return "No" 23 | case _: 24 | raise ValueError 25 | 26 | 27 | class Rule(BaseModel): 28 | class Config: 29 | frozen = True 30 | use_enum_values: Literal[True] 31 | 32 | name: str 33 | code: str 34 | linter: str 35 | summary: str 36 | message_formats: tuple[str, ...] 37 | fix: FixAvailability 38 | explanation: str 39 | preview: bool 40 | 41 | def as_dict(self) -> dict[str, Any]: 42 | return { 43 | "Code": self.code, 44 | "Name": self.name, 45 | "Fixable": self.fix.one_word, 46 | "Preview": self.preview, 47 | "Linter": self.linter, 48 | } 49 | 50 | @property 51 | def is_fixable(self): 52 | return self.fix in {FixAvailability.ALWAYS, FixAvailability.SOMETIMES} 53 | -------------------------------------------------------------------------------- /adopt_ruff/utils.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from collections.abc import Iterable 3 | from pathlib import Path 4 | 5 | from loguru import logger 6 | from mdutils.mdutils import MdUtils 7 | from tabulate import tabulate 8 | 9 | (ARTIFACTS_PATH := Path("artifacts")).mkdir(exist_ok=True) 10 | 11 | logger.add((ARTIFACTS_PATH / "adopt-ruff.log"), level="DEBUG") 12 | 13 | 14 | def make_collapsible(content: str, summary: str) -> str: 15 | return f"""
16 | {summary} 17 | 18 | {content} 19 | 20 |
21 | """ 22 | 23 | 24 | def table_to_csv(items: list[dict], path: Path) -> None: 25 | if not items: 26 | logger.warning(f"no items to write to {path.name}, skipping") 27 | return 28 | 29 | if len(items) > 1 and not all( 30 | # assert all keys are in the same order 31 | item.keys() == items[0].keys() 32 | for item in items[1:] 33 | ): 34 | raise ValueError("All table row keys must be identical, and in the same order") 35 | 36 | with path.open("w") as f: 37 | writer = csv.writer(f) 38 | writer.writerow(items[0].keys()) # Headers 39 | writer.writerows(item.values() for item in items) 40 | 41 | logger.debug(f"wrote {len(items)} to {path.absolute()!s}") 42 | 43 | 44 | def output_table( 45 | items: Iterable[dict], 46 | path: Path, 47 | md: MdUtils, 48 | collapsible: bool, 49 | collapsible_summary: str = "Details", 50 | ) -> None: 51 | """ 52 | Creates a markdown table, and saves to a CSV 53 | """ 54 | 55 | md_table = tabulate( 56 | [make_name_clickable(item) for item in items], 57 | tablefmt="github", 58 | headers="keys", 59 | ) 60 | 61 | if collapsible: 62 | md_table = make_collapsible(md_table, summary=collapsible_summary) 63 | 64 | md.new_line(md_table) 65 | table_to_csv(list(items), path) 66 | 67 | 68 | def make_name_clickable(item: dict) -> dict: 69 | if not (name := item.get("Name")): 70 | return item 71 | return item | {"Name": f"[{name}](https://docs.astral.sh/ruff/rules/{name})"} 72 | 73 | 74 | def search_config_file(path: Path) -> Path | None: 75 | """ 76 | Searches for common configuration files under the given directory. 77 | """ 78 | for name in ("pyproject.toml", "ruff.toml", ".ruff.toml"): 79 | if (file_path := path / name).exists(): 80 | logger.info(f"found config file at {file_path.resolve()!s}") 81 | return file_path 82 | return None 83 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "adopt-ruff" 3 | version = "0.1.0" 4 | description = "Adopt Ruff faster" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [ 8 | "loguru>=0.7.3", 9 | "mdutils>=1.6.0", 10 | "more-itertools>=10.5.0", 11 | "packaging>=24.2", 12 | "pydantic>=2.10.4", 13 | "tabulate>=0.9.0", 14 | "typer>=0.15.1", 15 | ] 16 | 17 | [build-system] 18 | requires = ["hatchling"] 19 | build-backend = "hatchling.build" 20 | 21 | [dependency-groups] 22 | dev = [ 23 | "mypy>=1.14.0", 24 | "pre-commit>=4.0.1", 25 | "ruff>=0.8.4", 26 | ] 27 | typing = [ 28 | "types-tabulate>=0.9.0.20241207", 29 | ] 30 | [project.scripts] 31 | adopt-ruff = "adopt_ruff.main:main" 32 | 33 | 34 | [tool.ruff] 35 | target-version = "py311" 36 | 37 | [tool.ruff.lint] 38 | select = ["ALL"] 39 | ignore = [ 40 | "ISC001", 41 | "ANN", 42 | "D", 43 | "DTZ", 44 | "COM812", 45 | "EM102", 46 | "TRY003", 47 | "S113", 48 | "G003", 49 | "G004", 50 | "INP001", 51 | "S501", 52 | "PD901", 53 | "E501", 54 | "TD002", 55 | "TD003", 56 | "TD004", 57 | "FIX002", 58 | "EM101", 59 | "FBT001", 60 | "FBT002", 61 | "S603", 62 | "S607", 63 | "PLR0913", 64 | ] 65 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.11" 3 | 4 | [[package]] 5 | name = "adopt-ruff" 6 | version = "0.1.0" 7 | source = { editable = "." } 8 | dependencies = [ 9 | { name = "loguru" }, 10 | { name = "mdutils" }, 11 | { name = "more-itertools" }, 12 | { name = "packaging" }, 13 | { name = "pydantic" }, 14 | { name = "tabulate" }, 15 | { name = "typer" }, 16 | ] 17 | 18 | [package.dev-dependencies] 19 | dev = [ 20 | { name = "mypy" }, 21 | { name = "pre-commit" }, 22 | { name = "ruff" }, 23 | ] 24 | typing = [ 25 | { name = "types-tabulate" }, 26 | ] 27 | 28 | [package.metadata] 29 | requires-dist = [ 30 | { name = "loguru", specifier = ">=0.7.3" }, 31 | { name = "mdutils", specifier = ">=1.6.0" }, 32 | { name = "more-itertools", specifier = ">=10.5.0" }, 33 | { name = "packaging", specifier = ">=24.2" }, 34 | { name = "pydantic", specifier = ">=2.10.4" }, 35 | { name = "tabulate", specifier = ">=0.9.0" }, 36 | { name = "typer", specifier = ">=0.15.1" }, 37 | ] 38 | 39 | [package.metadata.requires-dev] 40 | dev = [ 41 | { name = "mypy", specifier = ">=1.14.0" }, 42 | { name = "pre-commit", specifier = ">=4.0.1" }, 43 | { name = "ruff", specifier = ">=0.8.4" }, 44 | ] 45 | typing = [{ name = "types-tabulate", specifier = ">=0.9.0.20241207" }] 46 | 47 | [[package]] 48 | name = "annotated-types" 49 | version = "0.7.0" 50 | source = { registry = "https://pypi.org/simple" } 51 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 52 | wheels = [ 53 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 54 | ] 55 | 56 | [[package]] 57 | name = "cfgv" 58 | version = "3.4.0" 59 | source = { registry = "https://pypi.org/simple" } 60 | sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } 61 | wheels = [ 62 | { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, 63 | ] 64 | 65 | [[package]] 66 | name = "click" 67 | version = "8.1.7" 68 | source = { registry = "https://pypi.org/simple" } 69 | dependencies = [ 70 | { name = "colorama", marker = "sys_platform == 'win32'" }, 71 | ] 72 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } 73 | wheels = [ 74 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, 75 | ] 76 | 77 | [[package]] 78 | name = "colorama" 79 | version = "0.4.6" 80 | source = { registry = "https://pypi.org/simple" } 81 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 82 | wheels = [ 83 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 84 | ] 85 | 86 | [[package]] 87 | name = "distlib" 88 | version = "0.3.9" 89 | source = { registry = "https://pypi.org/simple" } 90 | sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } 91 | wheels = [ 92 | { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, 93 | ] 94 | 95 | [[package]] 96 | name = "filelock" 97 | version = "3.16.1" 98 | source = { registry = "https://pypi.org/simple" } 99 | sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } 100 | wheels = [ 101 | { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, 102 | ] 103 | 104 | [[package]] 105 | name = "identify" 106 | version = "2.6.3" 107 | source = { registry = "https://pypi.org/simple" } 108 | sdist = { url = "https://files.pythonhosted.org/packages/1a/5f/05f0d167be94585d502b4adf8c7af31f1dc0b1c7e14f9938a88fdbbcf4a7/identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02", size = 99179 } 109 | wheels = [ 110 | { url = "https://files.pythonhosted.org/packages/c9/f5/09644a3ad803fae9eca8efa17e1f2aef380c7f0b02f7ec4e8d446e51d64a/identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd", size = 99049 }, 111 | ] 112 | 113 | [[package]] 114 | name = "loguru" 115 | version = "0.7.3" 116 | source = { registry = "https://pypi.org/simple" } 117 | dependencies = [ 118 | { name = "colorama", marker = "sys_platform == 'win32'" }, 119 | { name = "win32-setctime", marker = "sys_platform == 'win32'" }, 120 | ] 121 | sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 } 122 | wheels = [ 123 | { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, 124 | ] 125 | 126 | [[package]] 127 | name = "markdown-it-py" 128 | version = "3.0.0" 129 | source = { registry = "https://pypi.org/simple" } 130 | dependencies = [ 131 | { name = "mdurl" }, 132 | ] 133 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } 134 | wheels = [ 135 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, 136 | ] 137 | 138 | [[package]] 139 | name = "mdurl" 140 | version = "0.1.2" 141 | source = { registry = "https://pypi.org/simple" } 142 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } 143 | wheels = [ 144 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, 145 | ] 146 | 147 | [[package]] 148 | name = "mdutils" 149 | version = "1.6.0" 150 | source = { registry = "https://pypi.org/simple" } 151 | sdist = { url = "https://files.pythonhosted.org/packages/b1/ec/6240f147530a2c8d362ed3f2f7985aca92cda68c25ffc2fc216504b17148/mdutils-1.6.0.tar.gz", hash = "sha256:647f3cf00df39fee6c57fa6738dc1160fce1788276b5530c87d43a70cdefdaf1", size = 22881 } 152 | 153 | [[package]] 154 | name = "more-itertools" 155 | version = "10.5.0" 156 | source = { registry = "https://pypi.org/simple" } 157 | sdist = { url = "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", size = 121020 } 158 | wheels = [ 159 | { url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 }, 160 | ] 161 | 162 | [[package]] 163 | name = "mypy" 164 | version = "1.14.0" 165 | source = { registry = "https://pypi.org/simple" } 166 | dependencies = [ 167 | { name = "mypy-extensions" }, 168 | { name = "typing-extensions" }, 169 | ] 170 | sdist = { url = "https://files.pythonhosted.org/packages/8c/7b/08046ef9330735f536a09a2e31b00f42bccdb2795dcd979636ba43bb2d63/mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6", size = 3215684 } 171 | wheels = [ 172 | { url = "https://files.pythonhosted.org/packages/34/c1/b9dd3e955953aec1c728992545b7877c9f6fa742a623ce4c200da0f62540/mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a", size = 11121032 }, 173 | { url = "https://files.pythonhosted.org/packages/ee/96/c52d5d516819ab95bf41f4a1ada828a3decc302f8c152ff4fc5feb0e4529/mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc", size = 10286294 }, 174 | { url = "https://files.pythonhosted.org/packages/69/2c/3dbe51877a24daa467f8d8631f9ffd1aabbf0f6d9367a01c44a59df81fe0/mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015", size = 12746528 }, 175 | { url = "https://files.pythonhosted.org/packages/a1/a8/eb20cde4ba9c4c3e20d958918a7c5d92210f4d1a0200c27de9a641f70996/mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb", size = 12883489 }, 176 | { url = "https://files.pythonhosted.org/packages/91/17/a1fc6c70f31d52c99299320cf81c3cb2c6b91ec7269414e0718a6d138e34/mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc", size = 9780113 }, 177 | { url = "https://files.pythonhosted.org/packages/fe/d8/0e72175ee0253217f5c44524f5e95251c02e95ba9749fb87b0e2074d203a/mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd", size = 11269011 }, 178 | { url = "https://files.pythonhosted.org/packages/e9/6d/4ea13839dabe5db588dc6a1b766da16f420d33cf118a7b7172cdf6c7fcb2/mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1", size = 10253076 }, 179 | { url = "https://files.pythonhosted.org/packages/3e/38/7db2c5d0f4d290e998f7a52b2e2616c7bbad96b8e04278ab09d11978a29e/mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63", size = 12862786 }, 180 | { url = "https://files.pythonhosted.org/packages/bf/4b/62d59c801b34141040989949c2b5c157d0408b45357335d3ec5b2845b0f6/mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d", size = 12971568 }, 181 | { url = "https://files.pythonhosted.org/packages/f1/9c/e0f281b32d70c87b9e4d2939e302b1ff77ada4d7b0f2fb32890c144bc1d6/mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba", size = 9879477 }, 182 | { url = "https://files.pythonhosted.org/packages/13/33/8380efd0ebdfdfac7fc0bf065f03a049800ca1e6c296ec1afc634340d992/mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741", size = 11251509 }, 183 | { url = "https://files.pythonhosted.org/packages/15/6d/4e1c21c60fee11af7d8e4f2902a29886d1387d6a836be16229eb3982a963/mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7", size = 10244282 }, 184 | { url = "https://files.pythonhosted.org/packages/8b/cf/7a8ae5c0161edae15d25c2c67c68ce8b150cbdc45aefc13a8be271ee80b2/mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8", size = 12867676 }, 185 | { url = "https://files.pythonhosted.org/packages/9c/d0/71f7bbdcc7cfd0f2892db5b13b1e8857673f2cc9e0c30e3e4340523dc186/mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc", size = 12964189 }, 186 | { url = "https://files.pythonhosted.org/packages/a7/40/fb4ad65d6d5f8c51396ecf6305ec0269b66013a5bf02d0e9528053640b4a/mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f", size = 9888247 }, 187 | { url = "https://files.pythonhosted.org/packages/39/32/0214608af400cdf8f5102144bb8af10d880675c65ed0b58f7e0e77175d50/mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab", size = 2752803 }, 188 | ] 189 | 190 | [[package]] 191 | name = "mypy-extensions" 192 | version = "1.0.0" 193 | source = { registry = "https://pypi.org/simple" } 194 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } 195 | wheels = [ 196 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, 197 | ] 198 | 199 | [[package]] 200 | name = "nodeenv" 201 | version = "1.9.1" 202 | source = { registry = "https://pypi.org/simple" } 203 | sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } 204 | wheels = [ 205 | { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, 206 | ] 207 | 208 | [[package]] 209 | name = "packaging" 210 | version = "24.2" 211 | source = { registry = "https://pypi.org/simple" } 212 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 213 | wheels = [ 214 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 215 | ] 216 | 217 | [[package]] 218 | name = "platformdirs" 219 | version = "4.3.6" 220 | source = { registry = "https://pypi.org/simple" } 221 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 222 | wheels = [ 223 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 224 | ] 225 | 226 | [[package]] 227 | name = "pre-commit" 228 | version = "4.0.1" 229 | source = { registry = "https://pypi.org/simple" } 230 | dependencies = [ 231 | { name = "cfgv" }, 232 | { name = "identify" }, 233 | { name = "nodeenv" }, 234 | { name = "pyyaml" }, 235 | { name = "virtualenv" }, 236 | ] 237 | sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 } 238 | wheels = [ 239 | { url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 }, 240 | ] 241 | 242 | [[package]] 243 | name = "pydantic" 244 | version = "2.10.4" 245 | source = { registry = "https://pypi.org/simple" } 246 | dependencies = [ 247 | { name = "annotated-types" }, 248 | { name = "pydantic-core" }, 249 | { name = "typing-extensions" }, 250 | ] 251 | sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 } 252 | wheels = [ 253 | { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 }, 254 | ] 255 | 256 | [[package]] 257 | name = "pydantic-core" 258 | version = "2.27.2" 259 | source = { registry = "https://pypi.org/simple" } 260 | dependencies = [ 261 | { name = "typing-extensions" }, 262 | ] 263 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } 264 | wheels = [ 265 | { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, 266 | { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, 267 | { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, 268 | { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, 269 | { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, 270 | { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, 271 | { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, 272 | { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, 273 | { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, 274 | { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, 275 | { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, 276 | { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, 277 | { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, 278 | { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, 279 | { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, 280 | { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, 281 | { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, 282 | { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, 283 | { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, 284 | { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, 285 | { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, 286 | { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, 287 | { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, 288 | { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, 289 | { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, 290 | { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, 291 | { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, 292 | { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, 293 | { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, 294 | { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, 295 | { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, 296 | { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, 297 | { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, 298 | { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, 299 | { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, 300 | { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, 301 | { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, 302 | { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, 303 | { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, 304 | { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, 305 | { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, 306 | { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, 307 | ] 308 | 309 | [[package]] 310 | name = "pygments" 311 | version = "2.18.0" 312 | source = { registry = "https://pypi.org/simple" } 313 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } 314 | wheels = [ 315 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, 316 | ] 317 | 318 | [[package]] 319 | name = "pyyaml" 320 | version = "6.0.2" 321 | source = { registry = "https://pypi.org/simple" } 322 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 323 | wheels = [ 324 | { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, 325 | { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, 326 | { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, 327 | { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, 328 | { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, 329 | { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, 330 | { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, 331 | { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, 332 | { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, 333 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 334 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 335 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 336 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 337 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 338 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 339 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 340 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 341 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 342 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 343 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 344 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 345 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 346 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 347 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 348 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 349 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 350 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 351 | ] 352 | 353 | [[package]] 354 | name = "rich" 355 | version = "13.9.4" 356 | source = { registry = "https://pypi.org/simple" } 357 | dependencies = [ 358 | { name = "markdown-it-py" }, 359 | { name = "pygments" }, 360 | ] 361 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } 362 | wheels = [ 363 | { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, 364 | ] 365 | 366 | [[package]] 367 | name = "ruff" 368 | version = "0.8.4" 369 | source = { registry = "https://pypi.org/simple" } 370 | sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } 371 | wheels = [ 372 | { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, 373 | { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, 374 | { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, 375 | { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, 376 | { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, 377 | { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, 378 | { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, 379 | { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, 380 | { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, 381 | { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, 382 | { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, 383 | { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, 384 | { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, 385 | { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, 386 | { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, 387 | { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, 388 | { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, 389 | ] 390 | 391 | [[package]] 392 | name = "shellingham" 393 | version = "1.5.4" 394 | source = { registry = "https://pypi.org/simple" } 395 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } 396 | wheels = [ 397 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, 398 | ] 399 | 400 | [[package]] 401 | name = "tabulate" 402 | version = "0.9.0" 403 | source = { registry = "https://pypi.org/simple" } 404 | sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } 405 | wheels = [ 406 | { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, 407 | ] 408 | 409 | [[package]] 410 | name = "typer" 411 | version = "0.15.1" 412 | source = { registry = "https://pypi.org/simple" } 413 | dependencies = [ 414 | { name = "click" }, 415 | { name = "rich" }, 416 | { name = "shellingham" }, 417 | { name = "typing-extensions" }, 418 | ] 419 | sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 } 420 | wheels = [ 421 | { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, 422 | ] 423 | 424 | [[package]] 425 | name = "types-tabulate" 426 | version = "0.9.0.20241207" 427 | source = { registry = "https://pypi.org/simple" } 428 | sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195 } 429 | wheels = [ 430 | { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307 }, 431 | ] 432 | 433 | [[package]] 434 | name = "typing-extensions" 435 | version = "4.12.2" 436 | source = { registry = "https://pypi.org/simple" } 437 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 438 | wheels = [ 439 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 440 | ] 441 | 442 | [[package]] 443 | name = "virtualenv" 444 | version = "20.28.0" 445 | source = { registry = "https://pypi.org/simple" } 446 | dependencies = [ 447 | { name = "distlib" }, 448 | { name = "filelock" }, 449 | { name = "platformdirs" }, 450 | ] 451 | sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } 452 | wheels = [ 453 | { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, 454 | ] 455 | 456 | [[package]] 457 | name = "win32-setctime" 458 | version = "1.2.0" 459 | source = { registry = "https://pypi.org/simple" } 460 | sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867 } 461 | wheels = [ 462 | { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 }, 463 | ] 464 | --------------------------------------------------------------------------------