diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index a26991a95..000000000 --- a/.isort.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[settings] -py_version=39 -known_first_party=anki,aqt,tests -profile=black diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 2413cc6c4..000000000 --- a/.pylintrc +++ /dev/null @@ -1,48 +0,0 @@ -[MASTER] -ignore-patterns=.*_pb2.* -persistent = no -extension-pkg-whitelist=orjson,PyQt6 -init-hook="import sys; sys.path.extend(['pylib/anki/_vendor', 'out/qt'])" - -[REPORTS] -output-format=colorized - -[MESSAGES CONTROL] -disable= - R, - line-too-long, - too-many-lines, - missing-function-docstring, - missing-module-docstring, - missing-class-docstring, - import-outside-toplevel, - wrong-import-position, - wrong-import-order, - fixme, - unused-wildcard-import, - attribute-defined-outside-init, - redefined-builtin, - wildcard-import, - broad-except, - bare-except, - unused-argument, - unused-variable, - redefined-outer-name, - global-statement, - protected-access, - arguments-differ, - arguments-renamed, - consider-using-f-string, - invalid-name, - broad-exception-raised - -[BASIC] -good-names = - id, - tr, - db, - ok, - ip, - -[IMPORTS] -ignored-modules = anki.*_pb2, anki.sync_pb2, win32file,pywintypes,socket,win32pipe,pyaudio,anki.scheduler_pb2,anki.notetypes_pb2 diff --git a/.ruff.toml b/.ruff.toml index fb6ffa2d8..4fa1ffea6 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,2 +1,91 @@ -target-version = "py39" -extend-exclude = [] +lint.select = [ + "E", # pycodestyle errors + "F", # Pyflakes errors + "PL", # Pylint rules + "I", # Isort rules + "ARG", + # "UP", # pyupgrade + # "B", # flake8-bugbear + # "SIM", # flake8-simplify +] + +extend-exclude = ["*_pb2.py", "*_pb2.pyi"] + +lint.ignore = [ + # Docstring rules (missing-*-docstring in pylint) + "D100", # Missing docstring in public module + "D101", # Missing docstring in public class + "D103", # Missing docstring in public function + + # Import rules (wrong-import-* in pylint) + "E402", # Module level import not at top of file + "E501", # Line too long + + # pycodestyle rules + "E741", # ambiguous-variable-name + + # Comment rules (fixme in pylint) + "FIX002", # Line contains TODO + + # Pyflakes rules + "F402", # import-shadowed-by-loop-var + "F403", # undefined-local-with-import-star + "F405", # undefined-local-with-import-star-usage + + # Naming rules (invalid-name in pylint) + "N801", # Class name should use CapWords convention + "N802", # Function name should be lowercase + "N803", # Argument name should be lowercase + "N806", # Variable in function should be lowercase + "N811", # Constant imported as non-constant + "N812", # Lowercase imported as non-lowercase + "N813", # Camelcase imported as lowercase + "N814", # Camelcase imported as constant + "N815", # Variable in class scope should not be mixedCase + "N816", # Variable in global scope should not be mixedCase + "N817", # CamelCase imported as acronym + "N818", # Error suffix in exception names + + # Pylint rules + "PLW0603", # global-statement + "PLW2901", # redefined-loop-name + "PLC0415", # import-outside-top-level + "PLR2004", # magic-value-comparison + + # Exception handling (broad-except, bare-except in pylint) + "BLE001", # Do not catch blind exception + + # Argument rules (unused-argument in pylint) + "ARG001", # Unused function argument + "ARG002", # Unused method argument + "ARG005", # Unused lambda argument + + # Access rules (protected-access in pylint) + "SLF001", # Private member accessed + + # String formatting (consider-using-f-string in pylint) + "UP032", # Use f-string instead of format call + + # Exception rules (broad-exception-raised in pylint) + "TRY301", # Abstract raise to an inner function + + # Builtin shadowing (redefined-builtin in pylint) + "A001", # Variable shadows a Python builtin + "A002", # Argument shadows a Python builtin + "A003", # Class attribute shadows a Python builtin +] + +[lint.per-file-ignores] +"**/anki/*_pb2.py" = ["ALL"] + +[lint.pep8-naming] +ignore-names = ["id", "tr", "db", "ok", "ip"] + +[lint.pylint] +max-args = 12 +max-returns = 10 +max-branches = 35 +max-statements = 125 + +[lint.isort] +known-first-party = ["anki", "aqt", "tests"] diff --git a/.vscode.dist/extensions.json b/.vscode.dist/extensions.json index bb449cc57..eb13662d6 100644 --- a/.vscode.dist/extensions.json +++ b/.vscode.dist/extensions.json @@ -2,7 +2,7 @@ "recommendations": [ "dprint.dprint", "ms-python.python", - "ms-python.black-formatter", + "charliermarsh.ruff", "rust-lang.rust-analyzer", "svelte.svelte-vscode", "zxh404.vscode-proto3", diff --git a/.vscode.dist/settings.json b/.vscode.dist/settings.json index ab91e06ab..0da294c38 100644 --- a/.vscode.dist/settings.json +++ b/.vscode.dist/settings.json @@ -18,7 +18,7 @@ "out/qt", "qt" ], - "python.formatting.provider": "black", + "python.formatting.provider": "charliermarsh.ruff", "python.linting.mypyEnabled": false, "python.analysis.diagnosticSeverityOverrides": { "reportMissingModuleSource": "none" diff --git a/build/configure/src/python.rs b/build/configure/src/python.rs index dde4dd51e..9d5e9057e 100644 --- a/build/configure/src/python.rs +++ b/build/configure/src/python.rs @@ -7,17 +7,14 @@ use anyhow::Result; use ninja_gen::action::BuildAction; use ninja_gen::archives::Platform; use ninja_gen::build::FilesHandle; -use ninja_gen::command::RunCommand; use ninja_gen::copy::CopyFiles; use ninja_gen::glob; -use ninja_gen::hashmap; use ninja_gen::input::BuildInput; use ninja_gen::inputs; use ninja_gen::python::python_format; use ninja_gen::python::PythonEnvironment; -use ninja_gen::python::PythonLint; use ninja_gen::python::PythonTypecheck; -use ninja_gen::rsync::RsyncFiles; +use ninja_gen::python::RuffCheck; use ninja_gen::Build; /// Normalize version string by removing leading zeros from numeric parts @@ -60,14 +57,7 @@ fn normalize_version(version: &str) -> String { } pub fn setup_venv(build: &mut Build) -> Result<()> { - let extra_binary_exports = &[ - "mypy", - "black", - "isort", - "pylint", - "pytest", - "protoc-gen-mypy", - ]; + let extra_binary_exports = &["mypy", "ruff", "pytest", "protoc-gen-mypy"]; build.add_action( "pyenv", PythonEnvironment { @@ -200,60 +190,26 @@ pub fn check_python(build: &mut Build) -> Result<()> { }, )?; - add_pylint(build)?; - - Ok(()) -} - -fn add_pylint(build: &mut Build) -> Result<()> { - // pylint does not support PEP420 implicit namespaces split across import paths, - // so we need to merge our pylib sources and generated files before invoking it, - // and add a top-level __init__.py + let ruff_folders = &["qt/aqt", "ftl", "pylib/tools", "tools", "python"]; + let ruff_deps = inputs![ + glob!["{pylib,ftl,qt,python,tools}/**/*.py"], + ":pylib:anki", + ":qt:aqt" + ]; build.add_action( - "check:pylint:copy_pylib", - RsyncFiles { - inputs: inputs![":pylib:anki"], - target_folder: "pylint/anki", - strip_prefix: "$builddir/pylib/anki", - // avoid copying our large rsbridge binary - extra_args: "--links", + "check:ruff", + RuffCheck { + folders: ruff_folders, + deps: ruff_deps.clone(), + check_only: true, }, )?; build.add_action( - "check:pylint:copy_pylib", - RsyncFiles { - inputs: inputs![glob!["pylib/anki/**"]], - target_folder: "pylint/anki", - strip_prefix: "pylib/anki", - extra_args: "", - }, - )?; - build.add_action( - "check:pylint:copy_pylib", - RunCommand { - command: ":pyenv:bin", - args: "$script $out", - inputs: hashmap! { "script" => inputs!["python/mkempty.py"] }, - outputs: hashmap! { "out" => vec!["pylint/anki/__init__.py"] }, - }, - )?; - build.add_action( - "check:pylint", - PythonLint { - folders: &[ - "$builddir/pylint/anki", - "qt/aqt", - "ftl", - "pylib/tools", - "tools", - "python", - ], - pylint_ini: inputs![".pylintrc"], - deps: inputs![ - ":check:pylint:copy_pylib", - ":qt:aqt", - glob!("{pylib/tools,ftl,qt,python,tools}/**/*.py") - ], + "fix:ruff", + RuffCheck { + folders: ruff_folders, + deps: ruff_deps, + check_only: false, }, )?; diff --git a/build/ninja_gen/src/python.rs b/build/ninja_gen/src/python.rs index 7ec11516c..541d6c96e 100644 --- a/build/ninja_gen/src/python.rs +++ b/build/ninja_gen/src/python.rs @@ -193,31 +193,19 @@ impl BuildAction for PythonTypecheck { struct PythonFormat<'a> { pub inputs: &'a BuildInput, pub check_only: bool, - pub isort_ini: &'a BuildInput, } impl BuildAction for PythonFormat<'_> { fn command(&self) -> &str { - "$black -t py39 -q $check --color $in && $ - $isort --color --settings-path $isort_ini $check $in" + "$ruff format $mode $in && $ruff check --select I --fix $in" } fn files(&mut self, build: &mut impl crate::build::FilesHandle) { build.add_inputs("in", self.inputs); - build.add_inputs("black", inputs![":pyenv:black"]); - build.add_inputs("isort", inputs![":pyenv:isort"]); + build.add_inputs("ruff", inputs![":pyenv:ruff"]); let hash = simple_hash(self.inputs); - build.add_env_var("BLACK_CACHE_DIR", "out/python/black.cache.{hash}"); - build.add_inputs("isort_ini", self.isort_ini); - build.add_variable( - "check", - if self.check_only { - "--diff --check" - } else { - "" - }, - ); + build.add_variable("mode", if self.check_only { "--check" } else { "" }); build.add_output_stamp(format!( "tests/python_format.{}.{hash}", @@ -227,13 +215,11 @@ impl BuildAction for PythonFormat<'_> { } pub fn python_format(build: &mut Build, group: &str, inputs: BuildInput) -> Result<()> { - let isort_ini = &inputs![".isort.cfg"]; build.add_action( format!("check:format:python:{group}"), PythonFormat { inputs: &inputs, check_only: true, - isort_ini, }, )?; @@ -242,34 +228,39 @@ pub fn python_format(build: &mut Build, group: &str, inputs: BuildInput) -> Resu PythonFormat { inputs: &inputs, check_only: false, - isort_ini, }, )?; Ok(()) } -pub struct PythonLint { +pub struct RuffCheck { pub folders: &'static [&'static str], - pub pylint_ini: BuildInput, pub deps: BuildInput, + pub check_only: bool, } -impl BuildAction for PythonLint { +impl BuildAction for RuffCheck { fn command(&self) -> &str { - "$pylint --rcfile $pylint_ini -sn -j $cpus $folders" + "$ruff check $folders $mode" } fn files(&mut self, build: &mut impl crate::build::FilesHandle) { build.add_inputs("", &self.deps); - build.add_inputs("pylint", inputs![":pyenv:pylint"]); - build.add_inputs("pylint_ini", &self.pylint_ini); + build.add_inputs("", inputs![".ruff.toml"]); + build.add_inputs("ruff", inputs![":pyenv:ruff"]); build.add_variable("folders", self.folders.join(" ")); - // On a 16 core system, values above 10 do not improve wall clock time, - // but waste extra cores that could be working on other tests. - build.add_variable("cpus", num_cpus::get().min(10).to_string()); + build.add_variable( + "mode", + if self.check_only { + "" + } else { + "--fix --unsafe-fixes" + }, + ); let hash = simple_hash(&self.deps); - build.add_output_stamp(format!("tests/python_lint.{hash}")); + let kind = if self.check_only { "check" } else { "fix" }; + build.add_output_stamp(format!("tests/python_ruff.{kind}.{hash}")); } } diff --git a/docs/development.md b/docs/development.md index c963aec02..defe9ef1e 100644 --- a/docs/development.md +++ b/docs/development.md @@ -85,7 +85,7 @@ When formatting issues are reported, they can be fixed with ./ninja format ``` -## Fixing eslint/copyright header issues +## Fixing ruff/eslint/copyright header issues ``` ./ninja fix diff --git a/docs/protobuf.md b/docs/protobuf.md index 29094fc65..75796b473 100644 --- a/docs/protobuf.md +++ b/docs/protobuf.md @@ -98,12 +98,6 @@ should preferably be assigned a number between 1 and 15. If a message contains Protobuf has an official Python implementation with an extensive [reference](https://developers.google.com/protocol-buffers/docs/reference/python-generated). -- Every message used in aqt or pylib must be added to the respective `.pylintrc` - to avoid failing type checks. The unqualified protobuf message's name must be - used, not an alias from `collection.py` for example. This should be taken into - account when choosing a message name in order to prevent skipping typechecking - a Python class of the same name. - ### Typescript Anki uses [protobuf-es](https://github.com/bufbuild/protobuf-es), which offers diff --git a/pylib/anki/_backend.py b/pylib/anki/_backend.py index a0d8f8949..03fbb30d6 100644 --- a/pylib/anki/_backend.py +++ b/pylib/anki/_backend.py @@ -46,7 +46,6 @@ from .errors import ( # the following comment is required to suppress a warning that only shows up # when there are other pylint failures -# pylint: disable=c-extension-no-member if _rsbridge.buildhash() != anki.buildinfo.buildhash: raise Exception( f"""rsbridge and anki build hashes do not match: @@ -164,7 +163,7 @@ class RustBackend(RustBackendGenerated): finally: elapsed = time.time() - start if current_thread() is main_thread() and elapsed > 0.2: - print(f"blocked main thread for {int(elapsed*1000)}ms:") + print(f"blocked main thread for {int(elapsed * 1000)}ms:") print("".join(traceback.format_stack())) err = backend_pb2.BackendError() diff --git a/pylib/anki/cards.py b/pylib/anki/cards.py index 093776ebb..02807ae73 100644 --- a/pylib/anki/cards.py +++ b/pylib/anki/cards.py @@ -7,7 +7,7 @@ import pprint import time from typing import NewType -import anki # pylint: disable=unused-import +import anki import anki.collection import anki.decks import anki.notes diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 6cf38174c..c64ffdb8b 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -158,7 +158,7 @@ class Collection(DeprecatedNamesMixin): self.tags = TagManager(self) self.conf = ConfigManager(self) self._load_scheduler() - self._startReps = 0 # pylint: disable=invalid-name + self._startReps = 0 def name(self) -> Any: return os.path.splitext(os.path.basename(self.path))[0] @@ -511,9 +511,7 @@ class Collection(DeprecatedNamesMixin): # Utils ########################################################################## - def nextID( # pylint: disable=invalid-name - self, type: str, inc: bool = True - ) -> Any: + def nextID(self, type: str, inc: bool = True) -> Any: type = f"next{type.capitalize()}" id = self.conf.get(type, 1) if inc: @@ -849,7 +847,6 @@ class Collection(DeprecatedNamesMixin): ) def _pb_search_separator(self, operator: SearchJoiner) -> SearchNode.Group.Joiner.V: - # pylint: disable=no-member if operator == "AND": return SearchNode.Group.Joiner.AND else: @@ -867,7 +864,9 @@ class Collection(DeprecatedNamesMixin): return column return None - def browser_row_for_id(self, id_: int) -> tuple[ + def browser_row_for_id( + self, id_: int + ) -> tuple[ Generator[tuple[str, bool, BrowserRow.Cell.TextElideMode.V], None, None], BrowserRow.Color.V, str, @@ -1212,8 +1211,6 @@ class Collection(DeprecatedNamesMixin): # the count on things like edits, which we probably could do by checking # the previous state in moveToState. - # pylint: disable=invalid-name - def startTimebox(self) -> None: self._startTime = time.time() self._startReps = self.sched.reps diff --git a/pylib/anki/exporting.py b/pylib/anki/exporting.py index 43713d8b2..ef6f02c63 100644 --- a/pylib/anki/exporting.py +++ b/pylib/anki/exporting.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name from __future__ import annotations @@ -351,7 +350,7 @@ class AnkiPackageExporter(AnkiExporter): colfile = path.replace(".apkg", ".anki2") AnkiExporter.exportInto(self, colfile) # prevent older clients from accessing - # pylint: disable=unreachable + self._addDummyCollection(z) z.write(colfile, "collection.anki21") diff --git a/pylib/anki/find.py b/pylib/anki/find.py index bcae6e556..106bf2876 100644 --- a/pylib/anki/find.py +++ b/pylib/anki/find.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name from __future__ import annotations diff --git a/pylib/anki/hooks.py b/pylib/anki/hooks.py index fcc3758f4..13148c649 100644 --- a/pylib/anki/hooks.py +++ b/pylib/anki/hooks.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name """ Tools for extending Anki. diff --git a/pylib/anki/importing/anki2.py b/pylib/anki/importing/anki2.py index 098265c3f..dcfa15c8d 100644 --- a/pylib/anki/importing/anki2.py +++ b/pylib/anki/importing/anki2.py @@ -1,7 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name + from __future__ import annotations import os diff --git a/pylib/anki/importing/apkg.py b/pylib/anki/importing/apkg.py index ea2325960..012686ffa 100644 --- a/pylib/anki/importing/apkg.py +++ b/pylib/anki/importing/apkg.py @@ -1,7 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name + from __future__ import annotations import json diff --git a/pylib/anki/importing/base.py b/pylib/anki/importing/base.py index 2ddcaaebf..fc27dc909 100644 --- a/pylib/anki/importing/base.py +++ b/pylib/anki/importing/base.py @@ -1,7 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name + from __future__ import annotations from typing import Any diff --git a/pylib/anki/importing/csvfile.py b/pylib/anki/importing/csvfile.py index 6a2ed347c..fde7ec8ac 100644 --- a/pylib/anki/importing/csvfile.py +++ b/pylib/anki/importing/csvfile.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name from __future__ import annotations @@ -144,7 +143,6 @@ class TextImporter(NoteImporter): self.close() zuper = super() if hasattr(zuper, "__del__"): - # pylint: disable=no-member zuper.__del__(self) # type: ignore def noteFromFields(self, fields: list[str]) -> ForeignNote: diff --git a/pylib/anki/importing/mnemo.py b/pylib/anki/importing/mnemo.py index 5b7fda65f..a2f68ad4c 100644 --- a/pylib/anki/importing/mnemo.py +++ b/pylib/anki/importing/mnemo.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name import re import time @@ -35,7 +34,6 @@ f._id=d._fact_id""" ): if id != curid: if note: - # pylint: disable=unsubscriptable-object notes[note["_id"]] = note note = {"_id": _id} curid = id @@ -185,7 +183,6 @@ acq_reps+ret_reps, lapses, card_type_id from cards""" state = dict(n=1) def repl(match): - # pylint: disable=cell-var-from-loop # replace [...] with cloze refs res = "{{c%d::%s}}" % (state["n"], match.group(1)) state["n"] += 1 diff --git a/pylib/anki/importing/noteimp.py b/pylib/anki/importing/noteimp.py index f827a525a..cb35a373a 100644 --- a/pylib/anki/importing/noteimp.py +++ b/pylib/anki/importing/noteimp.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name from __future__ import annotations diff --git a/pylib/anki/lang.py b/pylib/anki/lang.py index a0a6bf757..3cbb60319 100644 --- a/pylib/anki/lang.py +++ b/pylib/anki/lang.py @@ -157,13 +157,13 @@ def lang_to_disk_lang(lang: str) -> str: # the currently set interface language -current_lang = "en" # pylint: disable=invalid-name +current_lang = "en" # the current Fluent translation instance. Code in pylib/ should # not reference this, and should use col.tr instead. The global # instance exists for legacy reasons, and as a convenience for the # Qt code. -current_i18n: anki._backend.RustBackend | None = None # pylint: disable=invalid-name +current_i18n: anki._backend.RustBackend | None = None tr_legacyglobal = anki._backend.Translations(None) @@ -178,7 +178,7 @@ def ngettext(single: str, plural: str, num: int) -> str: def set_lang(lang: str) -> None: - global current_lang, current_i18n # pylint: disable=invalid-name + global current_lang, current_i18n current_lang = lang current_i18n = anki._backend.RustBackend(langs=[lang]) tr_legacyglobal.backend = weakref.ref(current_i18n) @@ -198,9 +198,7 @@ def get_def_lang(user_lang: str | None = None) -> tuple[int, str]: # getdefaultlocale() is deprecated since Python 3.11, but we need to keep using it as getlocale() behaves differently: https://bugs.python.org/issue38805 with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) - (sys_lang, enc) = ( - locale.getdefaultlocale() # pylint: disable=deprecated-method - ) + (sys_lang, enc) = locale.getdefaultlocale() except AttributeError: # this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US # further below diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 230084359..a2267663a 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -10,7 +10,7 @@ import time from collections.abc import Sequence from typing import Any, NewType, Union -import anki # pylint: disable=unused-import +import anki import anki.collection import anki.notes from anki import notetypes_pb2 @@ -419,7 +419,7 @@ and notes.mid = ? and cards.ord = ?""", # legacy API - used by unit tests and add-ons - def change( # pylint: disable=invalid-name + def change( self, notetype: NotetypeDict, nids: list[anki.notes.NoteId], @@ -478,8 +478,6 @@ and notes.mid = ? and cards.ord = ?""", # Legacy ########################################################################## - # pylint: disable=invalid-name - @deprecated(info="use note.cloze_numbers_in_fields()") def _availClozeOrds( self, notetype: NotetypeDict, flds: str, allow_empty: bool = True diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index 5de95bfb6..3d09d5632 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -7,7 +7,7 @@ import copy from collections.abc import Sequence from typing import NewType -import anki # pylint: disable=unused-import +import anki import anki.cards import anki.collection import anki.decks diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index 297c1b6d2..093712fca 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -4,10 +4,8 @@ # The backend code has moved into _backend; this file exists only to avoid breaking # some add-ons. They should be updated to point to the correct location in the # future. -# -# pylint: disable=unused-import -# pylint: enable=invalid-name +# ruff: noqa: F401 from anki.decks import DeckTreeNode from anki.errors import InvalidInput, NotFoundError from anki.lang import TR diff --git a/pylib/anki/scheduler/dummy.py b/pylib/anki/scheduler/dummy.py index 5732ad346..08896b1e5 100644 --- a/pylib/anki/scheduler/dummy.py +++ b/pylib/anki/scheduler/dummy.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name from __future__ import annotations diff --git a/pylib/anki/scheduler/legacy.py b/pylib/anki/scheduler/legacy.py index 58bed7933..35092588d 100644 --- a/pylib/anki/scheduler/legacy.py +++ b/pylib/anki/scheduler/legacy.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name from __future__ import annotations diff --git a/pylib/anki/scheduler/v3.py b/pylib/anki/scheduler/v3.py index 2a18ee021..3c1123d0b 100644 --- a/pylib/anki/scheduler/v3.py +++ b/pylib/anki/scheduler/v3.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=invalid-name """ The V3/2021 scheduler. @@ -184,7 +183,7 @@ class Scheduler(SchedulerBaseWithLegacy): return self._interval_for_filtered_state(state.filtered) else: assert_exhaustive(kind) - return 0 # pylint: disable=unreachable + return 0 def _interval_for_normal_state( self, normal: scheduler_pb2.SchedulingState.Normal @@ -200,7 +199,7 @@ class Scheduler(SchedulerBaseWithLegacy): return normal.relearning.learning.scheduled_secs else: assert_exhaustive(kind) - return 0 # pylint: disable=unreachable + return 0 def _interval_for_filtered_state( self, filtered: scheduler_pb2.SchedulingState.Filtered @@ -212,7 +211,7 @@ class Scheduler(SchedulerBaseWithLegacy): return self._interval_for_normal_state(filtered.rescheduling.original_state) else: assert_exhaustive(kind) - return 0 # pylint: disable=unreachable + return 0 def nextIvl(self, card: Card, ease: int) -> Any: "Don't use this - it is only required by tests, and will be moved in the future." diff --git a/pylib/anki/stats.py b/pylib/anki/stats.py index e6ca1cb97..e8045decb 100644 --- a/pylib/anki/stats.py +++ b/pylib/anki/stats.py @@ -1,7 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -# pylint: disable=C from __future__ import annotations @@ -27,7 +26,7 @@ def _legacy_card_stats( col: anki.collection.Collection, card_id: anki.cards.CardId, include_revlog: bool ) -> str: "A quick hack to preserve compatibility with the old HTML string API." - random_id = f"cardinfo-{base62(random.randint(0, 2 ** 64 - 1))}" + random_id = f"cardinfo-{base62(random.randint(0, 2**64 - 1))}" varName = random_id.replace("-", "") return f"""
@@ -324,7 +323,6 @@ group by day order by day""" yaxes=[dict(min=0), dict(position="right", min=0)], ) if days is not None: - # pylint: disable=invalid-unary-operand-type conf["xaxis"]["min"] = -days + 0.5 def plot(id: str, data: Any, ylabel: str, ylabel2: str) -> str: @@ -359,7 +357,6 @@ group by day order by day""" yaxes=[dict(min=0), dict(position="right", min=0)], ) if days is not None: - # pylint: disable=invalid-unary-operand-type conf["xaxis"]["min"] = -days + 0.5 def plot(id: str, data: Any, ylabel: str, ylabel2: str) -> str: diff --git a/pylib/anki/statsbg.py b/pylib/anki/statsbg.py index 552dfb5a9..b9ebb5aa8 100644 --- a/pylib/anki/statsbg.py +++ b/pylib/anki/statsbg.py @@ -1,5 +1,3 @@ -# pylint: disable=invalid-name - # from subtlepatterns.com; CC BY 4.0. # by Daniel Beaton # https://www.toptal.com/designers/subtlepatterns/fancy-deboss/ diff --git a/pylib/anki/stdmodels.py b/pylib/anki/stdmodels.py index 721b96bc6..4edb83a7a 100644 --- a/pylib/anki/stdmodels.py +++ b/pylib/anki/stdmodels.py @@ -12,7 +12,6 @@ from anki import notetypes_pb2 from anki._legacy import DeprecatedNamesMixinForModule from anki.utils import from_json_bytes -# pylint: disable=no-member StockNotetypeKind = notetypes_pb2.StockNotetype.Kind # add-on authors can add ("note type name", function) diff --git a/pylib/anki/tags.py b/pylib/anki/tags.py index a54aa7901..0c0338b82 100644 --- a/pylib/anki/tags.py +++ b/pylib/anki/tags.py @@ -16,7 +16,7 @@ import re from collections.abc import Collection, Sequence from typing import Match -import anki # pylint: disable=unused-import +import anki import anki.collection from anki import tags_pb2 from anki._legacy import DeprecatedNamesMixin, deprecated diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py index c61fd0588..60ae75507 100644 --- a/pylib/anki/utils.py +++ b/pylib/anki/utils.py @@ -24,7 +24,6 @@ from anki.dbproxy import DBProxy _tmpdir: str | None try: - # pylint: disable=c-extension-no-member import orjson to_json_bytes: Callable[[Any], bytes] = orjson.dumps @@ -156,12 +155,12 @@ def field_checksum(data: str) -> int: # Temp files ############################################################################## -_tmpdir = None # pylint: disable=invalid-name +_tmpdir = None def tmpdir() -> str: "A reusable temp folder which we clean out on each program invocation." - global _tmpdir # pylint: disable=invalid-name + global _tmpdir if not _tmpdir: def cleanup() -> None: @@ -216,7 +215,6 @@ def call(argv: list[str], wait: bool = True, **kwargs: Any) -> int: try: info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore except Exception: - # pylint: disable=no-member info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore else: info = None @@ -282,7 +280,7 @@ def plat_desc() -> str: elif is_win: theos = f"win:{platform.win32_ver()[0]}" elif system == "Linux": - import distro # pytype: disable=import-error # pylint: disable=import-error + import distro # pytype: disable=import-error dist_id = distro.id() dist_version = distro.version() diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index d9c2c1f87..236096572 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -169,8 +169,7 @@ def test_find_cards(): # properties id = col.db.scalar("select id from cards limit 1") col.db.execute( - "update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 " - "where id = ?", + "update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 where id = ?", id, ) assert len(col.find_cards("prop:ivl>5")) == 1 diff --git a/pylib/tests/test_schedv3.py b/pylib/tests/test_schedv3.py index 0deff7bf9..a71fa7140 100644 --- a/pylib/tests/test_schedv3.py +++ b/pylib/tests/test_schedv3.py @@ -551,12 +551,10 @@ def test_bury(): col.addNote(note) c2 = note.cards()[0] # burying - col.sched.bury_cards([c.id], manual=True) # pylint: disable=unexpected-keyword-arg + col.sched.bury_cards([c.id], manual=True) c.load() assert c.queue == QUEUE_TYPE_MANUALLY_BURIED - col.sched.bury_cards( - [c2.id], manual=False - ) # pylint: disable=unexpected-keyword-arg + col.sched.bury_cards([c2.id], manual=False) c2.load() assert c2.queue == QUEUE_TYPE_SIBLING_BURIED diff --git a/pylib/tools/genbuildinfo.py b/pylib/tools/genbuildinfo.py index b997ca5b3..add188d41 100644 --- a/pylib/tools/genbuildinfo.py +++ b/pylib/tools/genbuildinfo.py @@ -15,6 +15,5 @@ with open(buildhash_file, "r", encoding="utf8") as f: with open(outpath, "w", encoding="utf8") as f: # if we switch to uppercase we'll need to add legacy aliases - f.write("# pylint: disable=invalid-name\n") f.write(f"version = '{version}'\n") f.write(f"buildhash = '{buildhash}'\n") diff --git a/pylib/tools/genhooks.py b/pylib/tools/genhooks.py index e0e4924be..3644e3e95 100644 --- a/pylib/tools/genhooks.py +++ b/pylib/tools/genhooks.py @@ -133,7 +133,7 @@ prefix = """\ # This file is automatically generated; edit tools/genhooks.py instead. # Please import from anki.hooks instead of this file. -# pylint: disable=unused-import +# ruff: noqa: F401 from __future__ import annotations diff --git a/pylib/tools/hookslib.py b/pylib/tools/hookslib.py index 8920cdcfc..99f08fa1e 100644 --- a/pylib/tools/hookslib.py +++ b/pylib/tools/hookslib.py @@ -7,7 +7,6 @@ Code for generating hooks. from __future__ import annotations -import os import subprocess import sys from dataclasses import dataclass @@ -204,9 +203,6 @@ def write_file(path: str, hooks: list[Hook], prefix: str, suffix: str): code += f"\n{suffix}" - # work around issue with latest black - if sys.platform == "win32" and "HOME" in os.environ: - os.environ["USERPROFILE"] = os.environ["HOME"] with open(path, "wb") as file: file.write(code.encode("utf8")) - subprocess.run([sys.executable, "-m", "black", "-q", path], check=True) + subprocess.run([sys.executable, "-m", "ruff", "format", "-q", path], check=True) diff --git a/pyproject.toml b/pyproject.toml index 2e47ee2f5..7de32ec73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,14 +7,11 @@ classifiers = ["Private :: Do Not Upload"] [dependency-groups] dev = [ - "black", - "isort", "mypy", "mypy-protobuf", - "pylint", + "ruff", "pytest", "PyChromeDevTools", - "colorama", # for isort --color "wheel", "hatchling", # for type checking hatch_build.py files "mock", diff --git a/python/sphinx/build.py b/python/sphinx/build.py index 7d979c510..61091e6e1 100644 --- a/python/sphinx/build.py +++ b/python/sphinx/build.py @@ -2,6 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import os import subprocess + os.environ["REPO_ROOT"] = os.path.abspath(".") subprocess.run(["out/pyenv/bin/sphinx-apidoc", "-o", "out/python/sphinx", "pylib", "qt"], check=True) subprocess.run(["out/pyenv/bin/sphinx-build", "out/python/sphinx", "out/python/sphinx/html"], check=True) diff --git a/qt/.isort.cfg b/qt/.isort.cfg deleted file mode 100644 index aa01f87c7..000000000 --- a/qt/.isort.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[settings] -py_version=39 -profile=black -known_first_party=anki,aqt -extend_skip=aqt/forms,hooks_gen.py diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 740dcbc9f..e0864b301 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations +# ruff: noqa: F401 import atexit import logging import os @@ -28,7 +29,7 @@ if sys.version_info[0] < 3 or sys.version_info[1] < 9: # ensure unicode filenames are supported try: "テスト".encode(sys.getfilesystemencoding()) -except UnicodeEncodeError as exc: +except UnicodeEncodeError: print("Anki requires a UTF-8 locale.") print("Please Google 'how to change locale on [your Linux distro]'") sys.exit(1) @@ -285,7 +286,6 @@ class NativeEventFilter(QAbstractNativeEventFilter): def nativeEventFilter( self, eventType: Any, message: Any ) -> tuple[bool, Any | None]: - if eventType == "windows_generic_MSG": import ctypes.wintypes @@ -558,7 +558,7 @@ def run() -> None: print(f"Starting Anki {_version}...") try: _run() - except Exception as e: + except Exception: traceback.print_exc() QMessageBox.critical( None, diff --git a/qt/aqt/_macos_helper.py b/qt/aqt/_macos_helper.py index 634d94756..27b368e80 100644 --- a/qt/aqt/_macos_helper.py +++ b/qt/aqt/_macos_helper.py @@ -6,8 +6,6 @@ from __future__ import annotations import sys if sys.platform == "darwin": - from anki_mac_helper import ( # pylint:disable=unused-import,import-error - macos_helper, - ) + from anki_mac_helper import macos_helper else: macos_helper = None diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index fdce9142a..a940fb208 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -927,7 +927,6 @@ class AddonsDialog(QDialog): or self.mgr.configAction(addon.dir_name) ) ) - return def _onAddonItemSelected(self, row_int: int) -> None: try: @@ -1457,7 +1456,9 @@ class ChooseAddonsToUpdateDialog(QDialog): layout.addWidget(addons_list_widget) self.addons_list_widget = addons_list_widget - button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # type: ignore + button_box = QDialogButtonBox( + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel + ) # type: ignore qconnect( button_box.button(QDialogButtonBox.StandardButton.Ok).clicked, self.accept ) diff --git a/qt/aqt/ankihub.py b/qt/aqt/ankihub.py index 4d3b00c8a..0ea9b6dac 100644 --- a/qt/aqt/ankihub.py +++ b/qt/aqt/ankihub.py @@ -36,7 +36,6 @@ def ankihub_login( username: str = "", password: str = "", ) -> None: - def on_future_done(fut: Future[str], username: str, password: str) -> None: try: token = fut.result() @@ -73,7 +72,6 @@ def ankihub_logout( on_success: Callable[[], None], token: str, ) -> None: - def logout() -> None: mw.pm.set_ankihub_username(None) mw.pm.set_ankihub_token(None) diff --git a/qt/aqt/browser/__init__.py b/qt/aqt/browser/__init__.py index ffff667e7..130167124 100644 --- a/qt/aqt/browser/__init__.py +++ b/qt/aqt/browser/__init__.py @@ -2,6 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations +# ruff: noqa: F401 import sys import aqt diff --git a/qt/aqt/browser/sidebar/__init__.py b/qt/aqt/browser/sidebar/__init__.py index 99ca8f7c4..555ed3cdd 100644 --- a/qt/aqt/browser/sidebar/__init__.py +++ b/qt/aqt/browser/sidebar/__init__.py @@ -1,5 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +# ruff: noqa: F401 from anki.utils import is_mac from aqt.theme import theme_manager diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index e28f166e9..22d7fa4a6 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -106,7 +106,7 @@ class SidebarTreeView(QTreeView): def _setup_style(self) -> None: # match window background color and tweak style bgcolor = QPalette().window().color().name() - border = theme_manager.var(colors.BORDER) + theme_manager.var(colors.BORDER) styles = [ "padding: 3px", "padding-right: 0px", @@ -711,7 +711,6 @@ class SidebarTreeView(QTreeView): def _flags_tree(self, root: SidebarItem) -> None: icon_off = "icons:flag-variant-off-outline.svg" - icon = "icons:flag-variant.svg" icon_outline = "icons:flag-variant-outline.svg" root = self._section_root( diff --git a/qt/aqt/browser/table/__init__.py b/qt/aqt/browser/table/__init__.py index bd666cf1a..c942dc30f 100644 --- a/qt/aqt/browser/table/__init__.py +++ b/qt/aqt/browser/table/__init__.py @@ -2,6 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations +# ruff: noqa: F401 import copy import time from collections.abc import Generator, Sequence diff --git a/qt/aqt/browser/table/model.py b/qt/aqt/browser/table/model.py index e8d3bb7b6..732e8c99c 100644 --- a/qt/aqt/browser/table/model.py +++ b/qt/aqt/browser/table/model.py @@ -105,11 +105,11 @@ class DataModel(QAbstractTableModel): row = CellRow(*self.col.browser_row_for_id(item)) except BackendError as e: return CellRow.disabled(self.len_columns(), str(e)) - except Exception as e: + except Exception: return CellRow.disabled( self.len_columns(), tr.errors_please_check_database() ) - except BaseException as e: + except BaseException: # fatal error like a panic in the backend - dump it to the # console so it gets picked up by the error handler import traceback diff --git a/qt/aqt/browser/table/state.py b/qt/aqt/browser/table/state.py index 8054d2597..4faf88611 100644 --- a/qt/aqt/browser/table/state.py +++ b/qt/aqt/browser/table/state.py @@ -59,7 +59,7 @@ class ItemState(ABC): # abstractproperty is deprecated but used due to mypy limitations # (https://github.com/python/mypy/issues/1362) - @abstractproperty # pylint: disable=deprecated-decorator + @abstractproperty def active_columns(self) -> list[str]: """Return the saved or default columns for the state.""" diff --git a/qt/aqt/browser/table/table.py b/qt/aqt/browser/table/table.py index fb921822b..e28075b3f 100644 --- a/qt/aqt/browser/table/table.py +++ b/qt/aqt/browser/table/table.py @@ -361,8 +361,7 @@ class Table: for m in self.col.models.all(): for t in m["tmpls"]: bsize = t.get("bsize", 0) - if bsize > curmax: - curmax = bsize + curmax = max(curmax, bsize) assert self._view is not None vh = self._view.verticalHeader() diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 388ae46c0..aec5326f4 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -221,7 +221,7 @@ class CardLayout(QDialog): ) for i in range(min(len(self.cloze_numbers), 9)): QShortcut( # type: ignore - QKeySequence(f"Alt+{i+1}"), + QKeySequence(f"Alt+{i + 1}"), self, activated=lambda n=i: self.pform.cloze_number_combo.setCurrentIndex(n), ) @@ -790,7 +790,7 @@ class CardLayout(QDialog): assert a is not None qconnect( a.triggered, - lambda: self.on_restore_to_default(), # pylint: disable=unnecessary-lambda + lambda: self.on_restore_to_default(), ) if not self._isCloze(): diff --git a/qt/aqt/debug_console.py b/qt/aqt/debug_console.py index a37d14010..54fa8a17a 100644 --- a/qt/aqt/debug_console.py +++ b/qt/aqt/debug_console.py @@ -294,7 +294,6 @@ class DebugConsole(QDialog): } self._captureOutput(True) try: - # pylint: disable=exec-used exec(text, vars) except Exception: self._output += traceback.format_exc() diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 77bd84220..5dc688155 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -386,9 +386,7 @@ class DeckBrowser: if b[0]: b[0] = tr.actions_shortcut_key(val=shortcut(b[0])) buf += """ -""" % tuple( - b - ) +""" % tuple(b) self.bottom.draw( buf=buf, link_handler=self._linkHandler, diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index e824af14e..f2f267097 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -343,7 +343,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too gui_hooks.editor_did_init_shortcuts(cuts, self) for row in cuts: if len(row) == 2: - keys, fn = row # pylint: disable=unbalanced-tuple-unpacking + keys, fn = row fn = self._addFocusCheck(fn) else: keys, fn, _ = row @@ -796,7 +796,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too def accept(file: str) -> None: self.resolve_media(file) - file = getFile( + getFile( parent=self.widget, title=tr.editing_add_media(), cb=cast(Callable[[Any], None], accept), @@ -999,7 +999,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too if html.find(">") < 0: return html - with warnings.catch_warnings() as w: + with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) doc = BeautifulSoup(html, "html.parser") @@ -1029,15 +1029,14 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too m = re.match(r"http://127.0.0.1:\d+/(.*)$", str(src)) if m: tag["src"] = m.group(1) - else: - # in external pastes, download remote media - if isinstance(src, str) and self.isURL(src): - fname = self._retrieveURL(src) - if fname: - tag["src"] = fname - elif isinstance(src, str) and src.startswith("data:image/"): - # and convert inlined data - tag["src"] = self.inlinedImageToFilename(str(src)) + # in external pastes, download remote media + elif isinstance(src, str) and self.isURL(src): + fname = self._retrieveURL(src) + if fname: + tag["src"] = fname + elif isinstance(src, str) and src.startswith("data:image/"): + # and convert inlined data + tag["src"] = self.inlinedImageToFilename(str(src)) html = str(doc) return html @@ -1102,7 +1101,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too ) filter = f"{tr.editing_media()} ({extension_filter})" - file = getFile( + getFile( parent=self.widget, title=tr.editing_add_media(), cb=cast(Callable[[Any], None], self.setup_mask_editor), diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py index 4ff024917..cadbaef0c 100644 --- a/qt/aqt/exporting.py +++ b/qt/aqt/exporting.py @@ -212,11 +212,10 @@ class ExportDialog(QDialog): if self.isVerbatim: msg = tr.exporting_collection_exported() self.mw.reopen() + elif self.isTextNote: + msg = tr.exporting_note_exported(count=self.exporter.count) else: - if self.isTextNote: - msg = tr.exporting_note_exported(count=self.exporter.count) - else: - msg = tr.exporting_card_exported(count=self.exporter.count) + msg = tr.exporting_card_exported(count=self.exporter.count) gui_hooks.legacy_exporter_did_export(self.exporter) tooltip(msg, period=3000) QDialog.reject(self) diff --git a/qt/aqt/forms/__init__.py b/qt/aqt/forms/__init__.py index 9484f91ec..7cbfe3a6f 100644 --- a/qt/aqt/forms/__init__.py +++ b/qt/aqt/forms/__init__.py @@ -1,3 +1,4 @@ +# ruff: noqa: F401 from . import ( about, addcards, diff --git a/qt/aqt/import_export/importing.py b/qt/aqt/import_export/importing.py index 938824035..cb27c5e4b 100644 --- a/qt/aqt/import_export/importing.py +++ b/qt/aqt/import_export/importing.py @@ -134,9 +134,8 @@ IMPORTERS: list[type[Importer]] = [ def legacy_file_endings(col: Collection) -> list[str]: - from anki.importing import AnkiPackageImporter + from anki.importing import AnkiPackageImporter, TextImporter, importers from anki.importing import MnemosyneImporter as LegacyMnemosyneImporter - from anki.importing import TextImporter, importers return [ ext diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py index 8f9741a77..8701f9843 100644 --- a/qt/aqt/importing.py +++ b/qt/aqt/importing.py @@ -11,10 +11,10 @@ from collections.abc import Callable from concurrent.futures import Future from typing import Any -import anki.importing as importing import aqt.deckchooser import aqt.forms import aqt.modelchooser +from anki import importing from anki.importing.anki2 import MediaMapInvalid, V2ImportIntoV1 from anki.importing.apkg import AnkiPackageImporter from aqt.import_export.importing import ColpkgImporter @@ -262,7 +262,7 @@ class ImportDialog(QDialog): self.mapwidget.setLayout(self.grid) self.grid.setContentsMargins(3, 3, 3, 3) self.grid.setSpacing(6) - for num in range(len(self.mapping)): # pylint: disable=consider-using-enumerate + for num in range(len(self.mapping)): text = tr.importing_field_of_file_is(val=num + 1) self.grid.addWidget(QLabel(text), num, 0) if self.mapping[num] == "_tags": @@ -357,7 +357,7 @@ def importFile(mw: AnkiQt, file: str) -> None: try: importer.open() mw.progress.finish() - diag = ImportDialog(mw, importer) + ImportDialog(mw, importer) except UnicodeDecodeError: mw.progress.finish() showUnicodeWarning() @@ -443,3 +443,4 @@ def setupApkgImport(mw: AnkiQt, importer: AnkiPackageImporter) -> bool: return True ColpkgImporter.do_import(mw, importer.file) return False + return False diff --git a/qt/aqt/main.py b/qt/aqt/main.py index b261cd34e..e2e6f8534 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -376,7 +376,6 @@ class AnkiQt(QMainWindow): def openProfile(self) -> None: name = self.pm.profiles()[self.profileForm.profiles.currentRow()] self.pm.load(name) - return def onOpenProfile(self, *, callback: Callable[[], None] | None = None) -> None: def on_done() -> None: @@ -451,7 +450,6 @@ class AnkiQt(QMainWindow): self.loadProfile() def onOpenBackup(self) -> None: - def do_open(path: str) -> None: if not askUser( tr.qt_misc_replace_your_collection_with_an_earlier2( @@ -677,7 +675,7 @@ class AnkiQt(QMainWindow): gui_hooks.collection_did_load(self.col) self.apply_collection_options() self.moveToState("deckBrowser") - except Exception as e: + except Exception: # dump error to stderr so it gets picked up by errors.py traceback.print_exc() @@ -774,7 +772,6 @@ class AnkiQt(QMainWindow): oldState = self.state cleanup = getattr(self, f"_{oldState}Cleanup", None) if cleanup: - # pylint: disable=not-callable cleanup(state) self.clearStateShortcuts() self.state = state @@ -821,7 +818,7 @@ class AnkiQt(QMainWindow): self.bottomWeb.hide_timer.start() def _reviewCleanup(self, newState: MainWindowState) -> None: - if newState != "resetRequired" and newState != "review": + if newState not in {"resetRequired", "review"}: self.reviewer.auto_advance_enabled = False self.reviewer.cleanup() self.toolbarWeb.elevate() diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 69ef054ec..c6e53f997 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -230,7 +230,11 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response: else: max_age = 60 * 60 return flask.send_file( - fullpath, mimetype=mimetype, conditional=True, max_age=max_age, download_name="foo" # type: ignore[call-arg] + fullpath, + mimetype=mimetype, + conditional=True, + max_age=max_age, + download_name="foo", # type: ignore[call-arg] ) else: print(f"Not found: {path}") diff --git a/qt/aqt/mpv.py b/qt/aqt/mpv.py index e86675e41..2586d024a 100644 --- a/qt/aqt/mpv.py +++ b/qt/aqt/mpv.py @@ -24,7 +24,7 @@ # # ------------------------------------------------------------------------------ -# pylint: disable=raise-missing-from + from __future__ import annotations import inspect @@ -66,7 +66,6 @@ class MPVTimeoutError(MPVError): if is_win: - # pylint: disable=import-error import pywintypes import win32file # pytype: disable=import-error import win32job @@ -138,15 +137,15 @@ class MPVBase: extended_info = win32job.QueryInformationJobObject( self._job, win32job.JobObjectExtendedLimitInformation ) - extended_info["BasicLimitInformation"][ - "LimitFlags" - ] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + extended_info["BasicLimitInformation"]["LimitFlags"] = ( + win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + ) win32job.SetInformationJobObject( self._job, win32job.JobObjectExtendedLimitInformation, extended_info, ) - handle = self._proc._handle # pylint: disable=no-member + handle = self._proc._handle win32job.AssignProcessToJobObject(self._job, handle) def _stop_process(self): @@ -193,7 +192,10 @@ class MPVBase: None, ) win32pipe.SetNamedPipeHandleState( - self._sock, 1, None, None # PIPE_NOWAIT + self._sock, + 1, + None, + None, # PIPE_NOWAIT ) except pywintypes.error as err: if err.args[0] == winerror.ERROR_FILE_NOT_FOUND: @@ -394,7 +396,7 @@ class MPVBase: return self._get_response(timeout) except MPVCommandError as e: raise MPVCommandError(f"{message['command']!r}: {e}") - except Exception as e: + except Exception: if _retry: print("mpv timed out, restarting") self._stop_process() @@ -501,7 +503,6 @@ class MPV(MPVBase): # Simulate an init event when the process and all callbacks have been # completely set up. if hasattr(self, "on_init"): - # pylint: disable=no-member self.on_init() # diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index 184a51cf5..b1fc9a119 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -113,7 +113,7 @@ class Overview: self.mw.moveToState("deckBrowser") elif url == "review": openLink(f"{aqt.appShared}info/{self.sid}?v={self.sidVer}") - elif url == "studymore" or url == "customStudy": + elif url in {"studymore", "customStudy"}: self.onStudyMore() elif url == "unbury": self.on_unbury() @@ -180,7 +180,6 @@ class Overview: ############################################################ def _renderPage(self) -> None: - but = self.mw.button deck = self.mw.col.decks.current() self.sid = deck.get("sharedFrom") if self.sid: @@ -307,9 +306,7 @@ class Overview: if b[0]: b[0] = tr.actions_shortcut_key(val=shortcut(b[0])) buf += """ -""" % tuple( - b - ) +""" % tuple(b) self.bottom.draw( buf=buf, link_handler=link_handler, diff --git a/qt/aqt/package.py b/qt/aqt/package.py index f1834b594..d6236c4cd 100644 --- a/qt/aqt/package.py +++ b/qt/aqt/package.py @@ -11,7 +11,7 @@ from pathlib import Path from anki.utils import is_mac -# pylint: disable=unused-import,import-error +# ruff: noqa: F401 def first_run_setup() -> None: """Code run the first time after install/upgrade. diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 6597f6705..919be170c 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -128,7 +128,7 @@ class ProfileManager: default_answer_keys = {ease_num: str(ease_num) for ease_num in range(1, 5)} last_run_version: int = 0 - def __init__(self, base: Path) -> None: # + def __init__(self, base: Path) -> None: "base should be retrieved via ProfileMangager.get_created_base_folder" ## Settings which should be forgotten each Anki restart self.session: dict[str, Any] = {} @@ -153,7 +153,7 @@ class ProfileManager: else: try: self.load(profile) - except Exception as exc: + except Exception: self.invalid_profile_provided_on_commandline = True # Profile load/save @@ -483,7 +483,11 @@ create table if not exists profiles code = obj[1] name = obj[0] r = QMessageBox.question( - None, "Anki", tr.profiles_confirm_lang_choice(lang=name), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No # type: ignore + None, + "Anki", + tr.profiles_confirm_lang_choice(lang=name), + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, # type: ignore ) if r != QMessageBox.StandardButton.Yes: return self.setDefaultLang(f.lang.currentRow()) diff --git a/qt/aqt/progress.py b/qt/aqt/progress.py index 8c45c44ee..cc7e750de 100644 --- a/qt/aqt/progress.py +++ b/qt/aqt/progress.py @@ -119,13 +119,12 @@ class ProgressManager: if not self._levels: # no current progress; safe to fire func() + elif repeat: + # skip this time; we'll fire again + pass else: - if repeat: - # skip this time; we'll fire again - pass - else: - # retry in 100ms - self.single_shot(100, func, requires_collection) + # retry in 100ms + self.single_shot(100, func, requires_collection) return handler diff --git a/qt/aqt/qt/__init__.py b/qt/aqt/qt/__init__.py index 11670e90c..730bc771b 100644 --- a/qt/aqt/qt/__init__.py +++ b/qt/aqt/qt/__init__.py @@ -2,7 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # make sure not to optimize imports on this file -# pylint: disable=unused-import +# ruff: noqa: F401 from __future__ import annotations import os @@ -23,7 +23,7 @@ def debug() -> None: from pdb import set_trace pyqtRemoveInputHook() - set_trace() # pylint: disable=forgotten-debug-statement + set_trace() if os.environ.get("DEBUG"): diff --git a/qt/aqt/qt/qt6.py b/qt/aqt/qt/qt6.py index 2d387aabf..dabed757b 100644 --- a/qt/aqt/qt/qt6.py +++ b/qt/aqt/qt/qt6.py @@ -2,8 +2,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # make sure not to optimize imports on this file -# pylint: disable=unused-import - +# ruff: noqa: F401 """ PyQt6 imports """ diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 6e34a7931..a8839c598 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -21,13 +21,11 @@ from anki.scheduler.base import ScheduleCardsAsNew from anki.scheduler.v3 import ( CardAnswer, QueuedCards, -) -from anki.scheduler.v3 import Scheduler as V3Scheduler -from anki.scheduler.v3 import ( SchedulingContext, SchedulingStates, SetSchedulingStatesRequest, ) +from anki.scheduler.v3 import Scheduler as V3Scheduler from anki.tags import MARKED_TAG from anki.types import assert_exhaustive from anki.utils import is_mac @@ -597,10 +595,9 @@ class Reviewer: def _shortcutKeys( self, ) -> Sequence[tuple[str, Callable] | tuple[Qt.Key, Callable]]: - - def generate_default_answer_keys() -> ( - Generator[tuple[str, partial], None, None] - ): + def generate_default_answer_keys() -> Generator[ + tuple[str, partial], None, None + ]: for ease in aqt.mw.pm.default_answer_keys: key = aqt.mw.pm.get_answer_key(ease) if not key: diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 8ff49024f..d20365232 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -101,7 +101,7 @@ def is_audio_file(fname: str) -> bool: return ext in AUDIO_EXTENSIONS -class SoundOrVideoPlayer(Player): # pylint: disable=abstract-method +class SoundOrVideoPlayer(Player): default_rank = 0 def rank_for_tag(self, tag: AVTag) -> int | None: @@ -111,7 +111,7 @@ class SoundOrVideoPlayer(Player): # pylint: disable=abstract-method return None -class SoundPlayer(Player): # pylint: disable=abstract-method +class SoundPlayer(Player): default_rank = 0 def rank_for_tag(self, tag: AVTag) -> int | None: @@ -121,7 +121,7 @@ class SoundPlayer(Player): # pylint: disable=abstract-method return None -class VideoPlayer(Player): # pylint: disable=abstract-method +class VideoPlayer(Player): default_rank = 0 def rank_for_tag(self, tag: AVTag) -> int | None: @@ -324,7 +324,7 @@ def retryWait(proc: subprocess.Popen) -> int: ########################################################################## -class SimpleProcessPlayer(Player): # pylint: disable=abstract-method +class SimpleProcessPlayer(Player): "A player that invokes a new process for each tag to play." args: list[str] = [] diff --git a/qt/aqt/stylesheets.py b/qt/aqt/stylesheets.py index 0721e76d2..a262e18b9 100644 --- a/qt/aqt/stylesheets.py +++ b/qt/aqt/stylesheets.py @@ -208,7 +208,7 @@ class CustomStyles: button_pressed_gradient( tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_END), - tm.var(colors.SHADOW) + tm.var(colors.SHADOW), ) }; }} @@ -340,7 +340,7 @@ class CustomStyles: }} QTabBar::tab:selected:hover {{ background: { - button_gradient( + button_gradient( tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), tm.var(colors.BUTTON_PRIMARY_GRADIENT_END), ) @@ -391,7 +391,7 @@ class CustomStyles: button_pressed_gradient( tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_END), - tm.var(colors.SHADOW) + tm.var(colors.SHADOW), ) } }} @@ -647,10 +647,12 @@ class CustomStyles: margin: -7px 0; }} QSlider::handle:hover {{ - background: {button_gradient( - tm.var(colors.BUTTON_GRADIENT_START), - tm.var(colors.BUTTON_GRADIENT_END), - )} + background: { + button_gradient( + tm.var(colors.BUTTON_GRADIENT_START), + tm.var(colors.BUTTON_GRADIENT_END), + ) + } }} """ diff --git a/qt/aqt/sync.py b/qt/aqt/sync.py index 5a4d5fd4c..bedc05f8e 100644 --- a/qt/aqt/sync.py +++ b/qt/aqt/sync.py @@ -44,7 +44,7 @@ def get_sync_status( ) -> None: auth = mw.pm.sync_auth() if not auth: - callback(SyncStatus(required=SyncStatus.NO_CHANGES)) # pylint:disable=no-member + callback(SyncStatus(required=SyncStatus.NO_CHANGES)) return def on_future_done(fut: Future[SyncStatus]) -> None: @@ -302,7 +302,6 @@ def sync_login( username: str = "", password: str = "", ) -> None: - def on_future_done(fut: Future[SyncAuth], username: str, password: str) -> None: try: auth = fut.result() @@ -374,7 +373,9 @@ def get_id_and_pass_from_user( g.addWidget(passwd, 1, 1) l2.setBuddy(passwd) vbox.addLayout(g) - bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) # type: ignore + bb = QDialogButtonBox( + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel + ) # type: ignore ok_button = bb.button(QDialogButtonBox.StandardButton.Ok) assert ok_button is not None ok_button.setAutoDefault(True) diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py index e06cf71c2..675eb9345 100644 --- a/qt/aqt/theme.py +++ b/qt/aqt/theme.py @@ -187,7 +187,7 @@ class ThemeManager: self, card_ord: int, night_mode: bool | None = None ) -> str: "Returns body classes used when showing a card." - return f"card card{card_ord+1} {self.body_class(night_mode, reviewer=True)}" + return f"card card{card_ord + 1} {self.body_class(night_mode, reviewer=True)}" def var(self, vars: dict[str, str]) -> str: """Given day/night colors/props, return the correct one for the current theme.""" @@ -213,13 +213,12 @@ class ThemeManager: return False elif theme == Theme.DARK: return True + elif is_win: + return get_windows_dark_mode() + elif is_mac: + return get_macos_dark_mode() else: - if is_win: - return get_windows_dark_mode() - elif is_mac: - return get_macos_dark_mode() - else: - return get_linux_dark_mode() + return get_linux_dark_mode() def apply_style(self) -> None: "Apply currently configured style." @@ -340,7 +339,7 @@ def get_windows_dark_mode() -> bool: if not is_win: return False - from winreg import ( # type: ignore[attr-defined] # pylint: disable=import-error + from winreg import ( # type: ignore[attr-defined] HKEY_CURRENT_USER, OpenKey, QueryValueEx, @@ -352,7 +351,7 @@ def get_windows_dark_mode() -> bool: r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", ) return not QueryValueEx(key, "AppsUseLightTheme")[0] - except Exception as err: + except Exception: # key reportedly missing or set to wrong type on some systems return False @@ -416,12 +415,12 @@ def get_linux_dark_mode() -> bool: capture_output=True, encoding="utf8", ) - except FileNotFoundError as e: + except FileNotFoundError: # detection strategy failed, missing program # print(e) continue - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError: # detection strategy failed, command returned error # print(e) continue diff --git a/qt/aqt/tts.py b/qt/aqt/tts.py index 079a5e3de..d559fb41f 100644 --- a/qt/aqt/tts.py +++ b/qt/aqt/tts.py @@ -166,7 +166,6 @@ class MacVoice(TTSVoice): original_name: str -# pylint: disable=no-member class MacTTSPlayer(TTSProcessPlayer): "Invokes a process to play the audio in the background." @@ -487,7 +486,7 @@ if is_win: class WindowsTTSPlayer(TTSProcessPlayer): default_rank = -1 try: - import win32com.client # pylint: disable=import-error + import win32com.client speaker = win32com.client.Dispatch("SAPI.SpVoice") except Exception as exc: diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index d0252f69d..64d057082 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -19,7 +19,7 @@ from send2trash import send2trash import aqt from anki._legacy import DeprecatedNamesMixinForModule from anki.collection import Collection, HelpPage -from anki.lang import TR, tr_legacyglobal # pylint: disable=unused-import +from anki.lang import TR, tr_legacyglobal # noqa: F401 from anki.utils import ( call, invalid_filename, @@ -31,7 +31,7 @@ from anki.utils import ( from aqt.qt import * from aqt.qt import ( PYQT_VERSION_STR, - QT_VERSION_STR, + QT_VERSION_STR, # noqa: F401 QAction, QApplication, QCheckBox, @@ -294,7 +294,7 @@ def showInfo( icon = QMessageBox.Icon.Critical else: icon = QMessageBox.Icon.Information - mb = QMessageBox(parent_widget) # + mb = QMessageBox(parent_widget) if textFormat == "plain": mb.setTextFormat(Qt.TextFormat.PlainText) elif textFormat == "rich": @@ -967,8 +967,8 @@ def show_in_folder(path: str) -> None: def _show_in_folder_win32(path: str) -> None: - import win32con # pylint: disable=import-error - import win32gui # pylint: disable=import-error + import win32con + import win32gui from aqt import mw @@ -1263,12 +1263,12 @@ def opengl_vendor() -> str | None: # Can't use versionFunctions there return None - vp = QOpenGLVersionProfile() # type: ignore # pylint: disable=undefined-variable + vp = QOpenGLVersionProfile() # type: ignore vp.setVersion(2, 0) try: vf = ctx.versionFunctions(vp) # type: ignore - except ImportError as e: + except ImportError: return None if vf is None: diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 966d3de5a..95d84c00e 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -980,7 +980,6 @@ def _create_ankiwebview_subclass( /, **fixed_kwargs: Unpack[_AnkiWebViewKwargs], ) -> Type[AnkiWebView]: - def __init__(self, *args: Any, **kwargs: _AnkiWebViewKwargs) -> None: # user‑supplied kwargs override fixed kwargs merged = cast(_AnkiWebViewKwargs, {**fixed_kwargs, **kwargs}) diff --git a/qt/aqt/winpaths.py b/qt/aqt/winpaths.py index e53a47c06..8b2698739 100644 --- a/qt/aqt/winpaths.py +++ b/qt/aqt/winpaths.py @@ -100,7 +100,7 @@ _SHGetFolderPath.restype = _err_unless_zero def _get_path_buf(csidl): path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH) - result = _SHGetFolderPath(0, csidl, 0, 0, path_buf) + _SHGetFolderPath(0, csidl, 0, 0, path_buf) return path_buf.value diff --git a/rslib/proto/python.rs b/rslib/proto/python.rs index 6a7b5653b..a5adb4179 100644 --- a/rslib/proto/python.rs +++ b/rslib/proto/python.rs @@ -213,7 +213,6 @@ fn write_header(out: &mut impl Write) -> Result<()> { out.write_all( br#"# Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html -# pylint: skip-file from __future__ import annotations diff --git a/tools/reload_webviews.py b/tools/reload_webviews.py index 948401d9b..bb84c2554 100755 --- a/tools/reload_webviews.py +++ b/tools/reload_webviews.py @@ -43,11 +43,11 @@ except Exception as e: print_error( f"Could not establish connection to Chromium remote debugger. Is Anki Open? Exception:\n{e}" ) - exit(1) + sys.exit(1) if chrome.tabs is None: print_error("Was unable to get active web views.") - exit(1) + sys.exit(1) for tab_index, tab_data in enumerate(chrome.tabs): print(f"Reloading page: {tab_data['title']}") diff --git a/uv.lock b/uv.lock index 868bb55e6..08fd647b2 100644 --- a/uv.lock +++ b/uv.lock @@ -97,16 +97,13 @@ sphinx = [ [package.dev-dependencies] dev = [ - { name = "black" }, - { name = "colorama" }, { name = "hatchling" }, - { name = "isort" }, { name = "mock" }, { name = "mypy" }, { name = "mypy-protobuf" }, { name = "pychromedevtools" }, - { name = "pylint" }, { name = "pytest" }, + { name = "ruff" }, { name = "types-decorator" }, { name = "types-flask" }, { name = "types-flask-cors" }, @@ -129,16 +126,13 @@ provides-extras = ["sphinx"] [package.metadata.requires-dev] dev = [ - { name = "black" }, - { name = "colorama" }, { name = "hatchling" }, - { name = "isort" }, { name = "mock" }, { name = "mypy" }, { name = "mypy-protobuf" }, { name = "pychromedevtools" }, - { name = "pylint" }, { name = "pytest" }, + { name = "ruff" }, { name = "types-decorator" }, { name = "types-flask" }, { name = "types-flask-cors" }, @@ -298,45 +292,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, ] -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, - { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, - { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, - { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b6/ae7507470a4830dbbfe875c701e84a4a5fb9183d1497834871a715716a92/black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", size = 1628593, upload-time = "2025-01-29T05:37:23.672Z" }, - { url = "https://files.pythonhosted.org/packages/24/c1/ae36fa59a59f9363017ed397750a0cd79a470490860bc7713967d89cdd31/black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f", size = 1460000, upload-time = "2025-01-29T05:37:25.829Z" }, - { url = "https://files.pythonhosted.org/packages/ac/b6/98f832e7a6c49aa3a464760c67c7856363aa644f2f3c74cf7d624168607e/black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", size = 1765963, upload-time = "2025-01-29T04:18:38.116Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e9/2cb0a017eb7024f70e0d2e9bdb8c5a5b078c5740c7f8816065d06f04c557/black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", size = 1419419, upload-time = "2025-01-29T04:18:30.191Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, -] - [[package]] name = "blinker" version = "1.9.0" @@ -479,15 +434,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] -[[package]] -name = "dill" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, -] - [[package]] name = "distro" version = "1.9.0" @@ -589,7 +535,7 @@ name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp", marker = "python_full_version < '3.10' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, + { name = "zipp" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ @@ -605,15 +551,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, -] - [[package]] name = "itsdangerous" version = "2.2.0" @@ -664,14 +601,14 @@ wheels = [ [[package]] name = "markdown" -version = "3.8.1" +version = "3.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/7c/0738e5ff0adccd0b4e02c66d0446c03a3c557e02bb49b7c263d7ab56c57d/markdown-3.8.1.tar.gz", hash = "sha256:a2e2f01cead4828ee74ecca9623045f62216aef2212a7685d6eb9163f590b8c1", size = 361280, upload-time = "2025-06-18T14:50:49.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/34/3d1ff0cb4843a33817d06800e9383a2b2a2df4d508e37f53a40e829905d9/markdown-3.8.1-py3-none-any.whl", hash = "sha256:46cc0c0f1e5211ab2e9d453582f0b28a1bfaf058a9f7d5c50386b99b588d8811", size = 106642, upload-time = "2025-06-18T14:50:48.52Z" }, + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, ] [[package]] @@ -742,15 +679,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "mock" version = "5.2.0" @@ -965,15 +893,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/ce/608bbe82759363d6e752dd370daf066be3be8e7ffdb79838501ed6104173/pip_system_certs-5.2-py3-none-any.whl", hash = "sha256:e6ef3e106d4d02313e33955c2bcc4c2b143b2da07ef91e28a6805a0c1c512126", size = 5866, upload-time = "2025-06-17T23:33:14.554Z" }, ] -[[package]] -name = "platformdirs" -version = "4.3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -1011,31 +930,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/e0/04/572466be7e93af410 [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, -] - -[[package]] -name = "pylint" -version = "3.3.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astroid" }, - { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, - { name = "dill" }, - { name = "isort" }, - { name = "mccabe" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, - { name = "tomlkit" }, - { name = "typing-extensions", marker = "python_full_version < '3.10' or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt66') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt67') or (extra == 'extra-3-aqt-qt66' and extra == 'extra-3-aqt-qt69') or (extra == 'extra-3-aqt-qt67' and extra == 'extra-3-aqt-qt69')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/e4/83e487d3ddd64ab27749b66137b26dc0c5b5c161be680e6beffdc99070b3/pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", size = 1520709, upload-time = "2025-05-04T17:07:51.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/83/bff755d09e31b5d25cc7fdc4bf3915d1a404e181f1abf0359af376845c24/pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d", size = 522565, upload-time = "2025-05-04T17:07:48.714Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -1720,6 +1619,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/ff/f2150efc8daf0581d4dfaf0a2a30b08088b6df900230ee5ae4f7c8cd5163/rpds_py-0.25.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6099263f526efff9cf3883dfef505518730f7a7a93049b1d90d42e50a22b4793", size = 231305, upload-time = "2025-05-21T12:46:10.52Z" }, ] +[[package]] +name = "ruff" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426, upload-time = "2025-06-26T20:34:14.784Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649, upload-time = "2025-06-26T20:33:39.242Z" }, + { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201, upload-time = "2025-06-26T20:33:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769, upload-time = "2025-06-26T20:33:44.102Z" }, + { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902, upload-time = "2025-06-26T20:33:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002, upload-time = "2025-06-26T20:33:47.81Z" }, + { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522, upload-time = "2025-06-26T20:33:49.857Z" }, + { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264, upload-time = "2025-06-26T20:33:52.199Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882, upload-time = "2025-06-26T20:33:54.231Z" }, + { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941, upload-time = "2025-06-26T20:33:56.202Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887, upload-time = "2025-06-26T20:33:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742, upload-time = "2025-06-26T20:34:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909, upload-time = "2025-06-26T20:34:02.603Z" }, + { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005, upload-time = "2025-06-26T20:34:04.723Z" }, + { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579, upload-time = "2025-06-26T20:34:06.766Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495, upload-time = "2025-06-26T20:34:08.718Z" }, + { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485, upload-time = "2025-06-26T20:34:11.008Z" }, + { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209, upload-time = "2025-06-26T20:34:12.928Z" }, +] + [[package]] name = "send2trash" version = "1.8.3" @@ -1992,15 +1916,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "tomlkit" -version = "0.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, -] - [[package]] name = "trove-classifiers" version = "2025.5.9.12"