mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 05:52:22 -04:00
Switch to Ruff (#4119)
* Add check:ruff build action * Add fix:ruff action * Add Ruff config Mostly generated by Cursor * Handle rest of lints * Fix formatting * Replace black and isort with ruff-format * Run ruff-format * Fix lint errors * Remove pylint disables * Remove .pylintrc * Update docs * Fix check:format not just checking * Fix isort rule being ignored * Sort imports * Ensure ./ninja format also handles import sorting * Remove unused isort cfg * Enable unsafe fixes in fix:ruff, and enable unused var warning * Re-run on config change; enable unnecessary ARG ignores * Use all pycodestyle errors, and add some more commented-out ones Latter logged on https://github.com/ankitects/anki/issues/4135
This commit is contained in:
parent
b8963b463e
commit
f94d05bcbe
81 changed files with 315 additions and 476 deletions
|
@ -1,4 +0,0 @@
|
||||||
[settings]
|
|
||||||
py_version=39
|
|
||||||
known_first_party=anki,aqt,tests
|
|
||||||
profile=black
|
|
48
.pylintrc
48
.pylintrc
|
@ -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
|
|
93
.ruff.toml
93
.ruff.toml
|
@ -1,2 +1,91 @@
|
||||||
target-version = "py39"
|
lint.select = [
|
||||||
extend-exclude = []
|
"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"]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"dprint.dprint",
|
"dprint.dprint",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"ms-python.black-formatter",
|
"charliermarsh.ruff",
|
||||||
"rust-lang.rust-analyzer",
|
"rust-lang.rust-analyzer",
|
||||||
"svelte.svelte-vscode",
|
"svelte.svelte-vscode",
|
||||||
"zxh404.vscode-proto3",
|
"zxh404.vscode-proto3",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"out/qt",
|
"out/qt",
|
||||||
"qt"
|
"qt"
|
||||||
],
|
],
|
||||||
"python.formatting.provider": "black",
|
"python.formatting.provider": "charliermarsh.ruff",
|
||||||
"python.linting.mypyEnabled": false,
|
"python.linting.mypyEnabled": false,
|
||||||
"python.analysis.diagnosticSeverityOverrides": {
|
"python.analysis.diagnosticSeverityOverrides": {
|
||||||
"reportMissingModuleSource": "none"
|
"reportMissingModuleSource": "none"
|
||||||
|
|
|
@ -7,17 +7,14 @@ use anyhow::Result;
|
||||||
use ninja_gen::action::BuildAction;
|
use ninja_gen::action::BuildAction;
|
||||||
use ninja_gen::archives::Platform;
|
use ninja_gen::archives::Platform;
|
||||||
use ninja_gen::build::FilesHandle;
|
use ninja_gen::build::FilesHandle;
|
||||||
use ninja_gen::command::RunCommand;
|
|
||||||
use ninja_gen::copy::CopyFiles;
|
use ninja_gen::copy::CopyFiles;
|
||||||
use ninja_gen::glob;
|
use ninja_gen::glob;
|
||||||
use ninja_gen::hashmap;
|
|
||||||
use ninja_gen::input::BuildInput;
|
use ninja_gen::input::BuildInput;
|
||||||
use ninja_gen::inputs;
|
use ninja_gen::inputs;
|
||||||
use ninja_gen::python::python_format;
|
use ninja_gen::python::python_format;
|
||||||
use ninja_gen::python::PythonEnvironment;
|
use ninja_gen::python::PythonEnvironment;
|
||||||
use ninja_gen::python::PythonLint;
|
|
||||||
use ninja_gen::python::PythonTypecheck;
|
use ninja_gen::python::PythonTypecheck;
|
||||||
use ninja_gen::rsync::RsyncFiles;
|
use ninja_gen::python::RuffCheck;
|
||||||
use ninja_gen::Build;
|
use ninja_gen::Build;
|
||||||
|
|
||||||
/// Normalize version string by removing leading zeros from numeric parts
|
/// 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<()> {
|
pub fn setup_venv(build: &mut Build) -> Result<()> {
|
||||||
let extra_binary_exports = &[
|
let extra_binary_exports = &["mypy", "ruff", "pytest", "protoc-gen-mypy"];
|
||||||
"mypy",
|
|
||||||
"black",
|
|
||||||
"isort",
|
|
||||||
"pylint",
|
|
||||||
"pytest",
|
|
||||||
"protoc-gen-mypy",
|
|
||||||
];
|
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"pyenv",
|
"pyenv",
|
||||||
PythonEnvironment {
|
PythonEnvironment {
|
||||||
|
@ -200,60 +190,26 @@ pub fn check_python(build: &mut Build) -> Result<()> {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
add_pylint(build)?;
|
let ruff_folders = &["qt/aqt", "ftl", "pylib/tools", "tools", "python"];
|
||||||
|
let ruff_deps = inputs![
|
||||||
Ok(())
|
glob!["{pylib,ftl,qt,python,tools}/**/*.py"],
|
||||||
}
|
":pylib:anki",
|
||||||
|
":qt:aqt"
|
||||||
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
|
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"check:pylint:copy_pylib",
|
"check:ruff",
|
||||||
RsyncFiles {
|
RuffCheck {
|
||||||
inputs: inputs![":pylib:anki"],
|
folders: ruff_folders,
|
||||||
target_folder: "pylint/anki",
|
deps: ruff_deps.clone(),
|
||||||
strip_prefix: "$builddir/pylib/anki",
|
check_only: true,
|
||||||
// avoid copying our large rsbridge binary
|
|
||||||
extra_args: "--links",
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
build.add_action(
|
build.add_action(
|
||||||
"check:pylint:copy_pylib",
|
"fix:ruff",
|
||||||
RsyncFiles {
|
RuffCheck {
|
||||||
inputs: inputs![glob!["pylib/anki/**"]],
|
folders: ruff_folders,
|
||||||
target_folder: "pylint/anki",
|
deps: ruff_deps,
|
||||||
strip_prefix: "pylib/anki",
|
check_only: false,
|
||||||
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")
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -193,31 +193,19 @@ impl BuildAction for PythonTypecheck {
|
||||||
struct PythonFormat<'a> {
|
struct PythonFormat<'a> {
|
||||||
pub inputs: &'a BuildInput,
|
pub inputs: &'a BuildInput,
|
||||||
pub check_only: bool,
|
pub check_only: bool,
|
||||||
pub isort_ini: &'a BuildInput,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildAction for PythonFormat<'_> {
|
impl BuildAction for PythonFormat<'_> {
|
||||||
fn command(&self) -> &str {
|
fn command(&self) -> &str {
|
||||||
"$black -t py39 -q $check --color $in && $
|
"$ruff format $mode $in && $ruff check --select I --fix $in"
|
||||||
$isort --color --settings-path $isort_ini $check $in"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
|
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
|
||||||
build.add_inputs("in", self.inputs);
|
build.add_inputs("in", self.inputs);
|
||||||
build.add_inputs("black", inputs![":pyenv:black"]);
|
build.add_inputs("ruff", inputs![":pyenv:ruff"]);
|
||||||
build.add_inputs("isort", inputs![":pyenv:isort"]);
|
|
||||||
|
|
||||||
let hash = simple_hash(self.inputs);
|
let hash = simple_hash(self.inputs);
|
||||||
build.add_env_var("BLACK_CACHE_DIR", "out/python/black.cache.{hash}");
|
build.add_variable("mode", if self.check_only { "--check" } else { "" });
|
||||||
build.add_inputs("isort_ini", self.isort_ini);
|
|
||||||
build.add_variable(
|
|
||||||
"check",
|
|
||||||
if self.check_only {
|
|
||||||
"--diff --check"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
build.add_output_stamp(format!(
|
build.add_output_stamp(format!(
|
||||||
"tests/python_format.{}.{hash}",
|
"tests/python_format.{}.{hash}",
|
||||||
|
@ -227,13 +215,11 @@ impl BuildAction for PythonFormat<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn python_format(build: &mut Build, group: &str, inputs: BuildInput) -> Result<()> {
|
pub fn python_format(build: &mut Build, group: &str, inputs: BuildInput) -> Result<()> {
|
||||||
let isort_ini = &inputs![".isort.cfg"];
|
|
||||||
build.add_action(
|
build.add_action(
|
||||||
format!("check:format:python:{group}"),
|
format!("check:format:python:{group}"),
|
||||||
PythonFormat {
|
PythonFormat {
|
||||||
inputs: &inputs,
|
inputs: &inputs,
|
||||||
check_only: true,
|
check_only: true,
|
||||||
isort_ini,
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -242,34 +228,39 @@ pub fn python_format(build: &mut Build, group: &str, inputs: BuildInput) -> Resu
|
||||||
PythonFormat {
|
PythonFormat {
|
||||||
inputs: &inputs,
|
inputs: &inputs,
|
||||||
check_only: false,
|
check_only: false,
|
||||||
isort_ini,
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PythonLint {
|
pub struct RuffCheck {
|
||||||
pub folders: &'static [&'static str],
|
pub folders: &'static [&'static str],
|
||||||
pub pylint_ini: BuildInput,
|
|
||||||
pub deps: BuildInput,
|
pub deps: BuildInput,
|
||||||
|
pub check_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildAction for PythonLint {
|
impl BuildAction for RuffCheck {
|
||||||
fn command(&self) -> &str {
|
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) {
|
fn files(&mut self, build: &mut impl crate::build::FilesHandle) {
|
||||||
build.add_inputs("", &self.deps);
|
build.add_inputs("", &self.deps);
|
||||||
build.add_inputs("pylint", inputs![":pyenv:pylint"]);
|
build.add_inputs("", inputs![".ruff.toml"]);
|
||||||
build.add_inputs("pylint_ini", &self.pylint_ini);
|
build.add_inputs("ruff", inputs![":pyenv:ruff"]);
|
||||||
build.add_variable("folders", self.folders.join(" "));
|
build.add_variable("folders", self.folders.join(" "));
|
||||||
// On a 16 core system, values above 10 do not improve wall clock time,
|
build.add_variable(
|
||||||
// but waste extra cores that could be working on other tests.
|
"mode",
|
||||||
build.add_variable("cpus", num_cpus::get().min(10).to_string());
|
if self.check_only {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
"--fix --unsafe-fixes"
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let hash = simple_hash(&self.deps);
|
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}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ When formatting issues are reported, they can be fixed with
|
||||||
./ninja format
|
./ninja format
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fixing eslint/copyright header issues
|
## Fixing ruff/eslint/copyright header issues
|
||||||
|
|
||||||
```
|
```
|
||||||
./ninja fix
|
./ninja fix
|
||||||
|
|
|
@ -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).
|
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
|
### Typescript
|
||||||
|
|
||||||
Anki uses [protobuf-es](https://github.com/bufbuild/protobuf-es), which offers
|
Anki uses [protobuf-es](https://github.com/bufbuild/protobuf-es), which offers
|
||||||
|
|
|
@ -46,7 +46,6 @@ from .errors import (
|
||||||
|
|
||||||
# the following comment is required to suppress a warning that only shows up
|
# the following comment is required to suppress a warning that only shows up
|
||||||
# when there are other pylint failures
|
# when there are other pylint failures
|
||||||
# pylint: disable=c-extension-no-member
|
|
||||||
if _rsbridge.buildhash() != anki.buildinfo.buildhash:
|
if _rsbridge.buildhash() != anki.buildinfo.buildhash:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"""rsbridge and anki build hashes do not match:
|
f"""rsbridge and anki build hashes do not match:
|
||||||
|
@ -164,7 +163,7 @@ class RustBackend(RustBackendGenerated):
|
||||||
finally:
|
finally:
|
||||||
elapsed = time.time() - start
|
elapsed = time.time() - start
|
||||||
if current_thread() is main_thread() and elapsed > 0.2:
|
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()))
|
print("".join(traceback.format_stack()))
|
||||||
|
|
||||||
err = backend_pb2.BackendError()
|
err = backend_pb2.BackendError()
|
||||||
|
|
|
@ -7,7 +7,7 @@ import pprint
|
||||||
import time
|
import time
|
||||||
from typing import NewType
|
from typing import NewType
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki
|
||||||
import anki.collection
|
import anki.collection
|
||||||
import anki.decks
|
import anki.decks
|
||||||
import anki.notes
|
import anki.notes
|
||||||
|
|
|
@ -158,7 +158,7 @@ class Collection(DeprecatedNamesMixin):
|
||||||
self.tags = TagManager(self)
|
self.tags = TagManager(self)
|
||||||
self.conf = ConfigManager(self)
|
self.conf = ConfigManager(self)
|
||||||
self._load_scheduler()
|
self._load_scheduler()
|
||||||
self._startReps = 0 # pylint: disable=invalid-name
|
self._startReps = 0
|
||||||
|
|
||||||
def name(self) -> Any:
|
def name(self) -> Any:
|
||||||
return os.path.splitext(os.path.basename(self.path))[0]
|
return os.path.splitext(os.path.basename(self.path))[0]
|
||||||
|
@ -511,9 +511,7 @@ class Collection(DeprecatedNamesMixin):
|
||||||
# Utils
|
# Utils
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def nextID( # pylint: disable=invalid-name
|
def nextID(self, type: str, inc: bool = True) -> Any:
|
||||||
self, type: str, inc: bool = True
|
|
||||||
) -> Any:
|
|
||||||
type = f"next{type.capitalize()}"
|
type = f"next{type.capitalize()}"
|
||||||
id = self.conf.get(type, 1)
|
id = self.conf.get(type, 1)
|
||||||
if inc:
|
if inc:
|
||||||
|
@ -849,7 +847,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
def _pb_search_separator(self, operator: SearchJoiner) -> SearchNode.Group.Joiner.V:
|
def _pb_search_separator(self, operator: SearchJoiner) -> SearchNode.Group.Joiner.V:
|
||||||
# pylint: disable=no-member
|
|
||||||
if operator == "AND":
|
if operator == "AND":
|
||||||
return SearchNode.Group.Joiner.AND
|
return SearchNode.Group.Joiner.AND
|
||||||
else:
|
else:
|
||||||
|
@ -867,7 +864,9 @@ class Collection(DeprecatedNamesMixin):
|
||||||
return column
|
return column
|
||||||
return None
|
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],
|
Generator[tuple[str, bool, BrowserRow.Cell.TextElideMode.V], None, None],
|
||||||
BrowserRow.Color.V,
|
BrowserRow.Color.V,
|
||||||
str,
|
str,
|
||||||
|
@ -1212,8 +1211,6 @@ class Collection(DeprecatedNamesMixin):
|
||||||
# the count on things like edits, which we probably could do by checking
|
# the count on things like edits, which we probably could do by checking
|
||||||
# the previous state in moveToState.
|
# the previous state in moveToState.
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
def startTimebox(self) -> None:
|
def startTimebox(self) -> None:
|
||||||
self._startTime = time.time()
|
self._startTime = time.time()
|
||||||
self._startReps = self.sched.reps
|
self._startReps = self.sched.reps
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
@ -351,7 +350,7 @@ class AnkiPackageExporter(AnkiExporter):
|
||||||
colfile = path.replace(".apkg", ".anki2")
|
colfile = path.replace(".apkg", ".anki2")
|
||||||
AnkiExporter.exportInto(self, colfile)
|
AnkiExporter.exportInto(self, colfile)
|
||||||
# prevent older clients from accessing
|
# prevent older clients from accessing
|
||||||
# pylint: disable=unreachable
|
|
||||||
self._addDummyCollection(z)
|
self._addDummyCollection(z)
|
||||||
z.write(colfile, "collection.anki21")
|
z.write(colfile, "collection.anki21")
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Tools for extending Anki.
|
Tools for extending Anki.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
@ -144,7 +143,6 @@ class TextImporter(NoteImporter):
|
||||||
self.close()
|
self.close()
|
||||||
zuper = super()
|
zuper = super()
|
||||||
if hasattr(zuper, "__del__"):
|
if hasattr(zuper, "__del__"):
|
||||||
# pylint: disable=no-member
|
|
||||||
zuper.__del__(self) # type: ignore
|
zuper.__del__(self) # type: ignore
|
||||||
|
|
||||||
def noteFromFields(self, fields: list[str]) -> ForeignNote:
|
def noteFromFields(self, fields: list[str]) -> ForeignNote:
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
@ -35,7 +34,6 @@ f._id=d._fact_id"""
|
||||||
):
|
):
|
||||||
if id != curid:
|
if id != curid:
|
||||||
if note:
|
if note:
|
||||||
# pylint: disable=unsubscriptable-object
|
|
||||||
notes[note["_id"]] = note
|
notes[note["_id"]] = note
|
||||||
note = {"_id": _id}
|
note = {"_id": _id}
|
||||||
curid = id
|
curid = id
|
||||||
|
@ -185,7 +183,6 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""
|
||||||
state = dict(n=1)
|
state = dict(n=1)
|
||||||
|
|
||||||
def repl(match):
|
def repl(match):
|
||||||
# pylint: disable=cell-var-from-loop
|
|
||||||
# replace [...] with cloze refs
|
# replace [...] with cloze refs
|
||||||
res = "{{c%d::%s}}" % (state["n"], match.group(1))
|
res = "{{c%d::%s}}" % (state["n"], match.group(1))
|
||||||
state["n"] += 1
|
state["n"] += 1
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -157,13 +157,13 @@ def lang_to_disk_lang(lang: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
# the currently set interface language
|
# the currently set interface language
|
||||||
current_lang = "en" # pylint: disable=invalid-name
|
current_lang = "en"
|
||||||
|
|
||||||
# the current Fluent translation instance. Code in pylib/ should
|
# the current Fluent translation instance. Code in pylib/ should
|
||||||
# not reference this, and should use col.tr instead. The global
|
# not reference this, and should use col.tr instead. The global
|
||||||
# instance exists for legacy reasons, and as a convenience for the
|
# instance exists for legacy reasons, and as a convenience for the
|
||||||
# Qt code.
|
# 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)
|
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:
|
def set_lang(lang: str) -> None:
|
||||||
global current_lang, current_i18n # pylint: disable=invalid-name
|
global current_lang, current_i18n
|
||||||
current_lang = lang
|
current_lang = lang
|
||||||
current_i18n = anki._backend.RustBackend(langs=[lang])
|
current_i18n = anki._backend.RustBackend(langs=[lang])
|
||||||
tr_legacyglobal.backend = weakref.ref(current_i18n)
|
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
|
# 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():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore", DeprecationWarning)
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
(sys_lang, enc) = (
|
(sys_lang, enc) = locale.getdefaultlocale()
|
||||||
locale.getdefaultlocale() # pylint: disable=deprecated-method
|
|
||||||
)
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US
|
# this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US
|
||||||
# further below
|
# further below
|
||||||
|
|
|
@ -10,7 +10,7 @@ import time
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any, NewType, Union
|
from typing import Any, NewType, Union
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki
|
||||||
import anki.collection
|
import anki.collection
|
||||||
import anki.notes
|
import anki.notes
|
||||||
from anki import notetypes_pb2
|
from anki import notetypes_pb2
|
||||||
|
@ -419,7 +419,7 @@ and notes.mid = ? and cards.ord = ?""",
|
||||||
|
|
||||||
# legacy API - used by unit tests and add-ons
|
# legacy API - used by unit tests and add-ons
|
||||||
|
|
||||||
def change( # pylint: disable=invalid-name
|
def change(
|
||||||
self,
|
self,
|
||||||
notetype: NotetypeDict,
|
notetype: NotetypeDict,
|
||||||
nids: list[anki.notes.NoteId],
|
nids: list[anki.notes.NoteId],
|
||||||
|
@ -478,8 +478,6 @@ and notes.mid = ? and cards.ord = ?""",
|
||||||
# Legacy
|
# Legacy
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
@deprecated(info="use note.cloze_numbers_in_fields()")
|
@deprecated(info="use note.cloze_numbers_in_fields()")
|
||||||
def _availClozeOrds(
|
def _availClozeOrds(
|
||||||
self, notetype: NotetypeDict, flds: str, allow_empty: bool = True
|
self, notetype: NotetypeDict, flds: str, allow_empty: bool = True
|
||||||
|
|
|
@ -7,7 +7,7 @@ import copy
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import NewType
|
from typing import NewType
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki
|
||||||
import anki.cards
|
import anki.cards
|
||||||
import anki.collection
|
import anki.collection
|
||||||
import anki.decks
|
import anki.decks
|
||||||
|
|
|
@ -4,10 +4,8 @@
|
||||||
# The backend code has moved into _backend; this file exists only to avoid breaking
|
# 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
|
# some add-ons. They should be updated to point to the correct location in the
|
||||||
# future.
|
# future.
|
||||||
#
|
|
||||||
# pylint: disable=unused-import
|
|
||||||
# pylint: enable=invalid-name
|
|
||||||
|
|
||||||
|
# ruff: noqa: F401
|
||||||
from anki.decks import DeckTreeNode
|
from anki.decks import DeckTreeNode
|
||||||
from anki.errors import InvalidInput, NotFoundError
|
from anki.errors import InvalidInput, NotFoundError
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The V3/2021 scheduler.
|
The V3/2021 scheduler.
|
||||||
|
@ -184,7 +183,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
return self._interval_for_filtered_state(state.filtered)
|
return self._interval_for_filtered_state(state.filtered)
|
||||||
else:
|
else:
|
||||||
assert_exhaustive(kind)
|
assert_exhaustive(kind)
|
||||||
return 0 # pylint: disable=unreachable
|
return 0
|
||||||
|
|
||||||
def _interval_for_normal_state(
|
def _interval_for_normal_state(
|
||||||
self, normal: scheduler_pb2.SchedulingState.Normal
|
self, normal: scheduler_pb2.SchedulingState.Normal
|
||||||
|
@ -200,7 +199,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
return normal.relearning.learning.scheduled_secs
|
return normal.relearning.learning.scheduled_secs
|
||||||
else:
|
else:
|
||||||
assert_exhaustive(kind)
|
assert_exhaustive(kind)
|
||||||
return 0 # pylint: disable=unreachable
|
return 0
|
||||||
|
|
||||||
def _interval_for_filtered_state(
|
def _interval_for_filtered_state(
|
||||||
self, filtered: scheduler_pb2.SchedulingState.Filtered
|
self, filtered: scheduler_pb2.SchedulingState.Filtered
|
||||||
|
@ -212,7 +211,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
return self._interval_for_normal_state(filtered.rescheduling.original_state)
|
return self._interval_for_normal_state(filtered.rescheduling.original_state)
|
||||||
else:
|
else:
|
||||||
assert_exhaustive(kind)
|
assert_exhaustive(kind)
|
||||||
return 0 # pylint: disable=unreachable
|
return 0
|
||||||
|
|
||||||
def nextIvl(self, card: Card, ease: int) -> Any:
|
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."
|
"Don't use this - it is only required by tests, and will be moved in the future."
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# pylint: disable=C
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ def _legacy_card_stats(
|
||||||
col: anki.collection.Collection, card_id: anki.cards.CardId, include_revlog: bool
|
col: anki.collection.Collection, card_id: anki.cards.CardId, include_revlog: bool
|
||||||
) -> str:
|
) -> str:
|
||||||
"A quick hack to preserve compatibility with the old HTML string API."
|
"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("-", "")
|
varName = random_id.replace("-", "")
|
||||||
return f"""
|
return f"""
|
||||||
<div id="{random_id}"></div>
|
<div id="{random_id}"></div>
|
||||||
|
@ -324,7 +323,6 @@ group by day order by day"""
|
||||||
yaxes=[dict(min=0), dict(position="right", min=0)],
|
yaxes=[dict(min=0), dict(position="right", min=0)],
|
||||||
)
|
)
|
||||||
if days is not None:
|
if days is not None:
|
||||||
# pylint: disable=invalid-unary-operand-type
|
|
||||||
conf["xaxis"]["min"] = -days + 0.5
|
conf["xaxis"]["min"] = -days + 0.5
|
||||||
|
|
||||||
def plot(id: str, data: Any, ylabel: str, ylabel2: str) -> str:
|
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)],
|
yaxes=[dict(min=0), dict(position="right", min=0)],
|
||||||
)
|
)
|
||||||
if days is not None:
|
if days is not None:
|
||||||
# pylint: disable=invalid-unary-operand-type
|
|
||||||
conf["xaxis"]["min"] = -days + 0.5
|
conf["xaxis"]["min"] = -days + 0.5
|
||||||
|
|
||||||
def plot(id: str, data: Any, ylabel: str, ylabel2: str) -> str:
|
def plot(id: str, data: Any, ylabel: str, ylabel2: str) -> str:
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# pylint: disable=invalid-name
|
|
||||||
|
|
||||||
# from subtlepatterns.com; CC BY 4.0.
|
# from subtlepatterns.com; CC BY 4.0.
|
||||||
# by Daniel Beaton
|
# by Daniel Beaton
|
||||||
# https://www.toptal.com/designers/subtlepatterns/fancy-deboss/
|
# https://www.toptal.com/designers/subtlepatterns/fancy-deboss/
|
||||||
|
|
|
@ -12,7 +12,6 @@ from anki import notetypes_pb2
|
||||||
from anki._legacy import DeprecatedNamesMixinForModule
|
from anki._legacy import DeprecatedNamesMixinForModule
|
||||||
from anki.utils import from_json_bytes
|
from anki.utils import from_json_bytes
|
||||||
|
|
||||||
# pylint: disable=no-member
|
|
||||||
StockNotetypeKind = notetypes_pb2.StockNotetype.Kind
|
StockNotetypeKind = notetypes_pb2.StockNotetype.Kind
|
||||||
|
|
||||||
# add-on authors can add ("note type name", function)
|
# add-on authors can add ("note type name", function)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import re
|
||||||
from collections.abc import Collection, Sequence
|
from collections.abc import Collection, Sequence
|
||||||
from typing import Match
|
from typing import Match
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki
|
||||||
import anki.collection
|
import anki.collection
|
||||||
from anki import tags_pb2
|
from anki import tags_pb2
|
||||||
from anki._legacy import DeprecatedNamesMixin, deprecated
|
from anki._legacy import DeprecatedNamesMixin, deprecated
|
||||||
|
|
|
@ -24,7 +24,6 @@ from anki.dbproxy import DBProxy
|
||||||
_tmpdir: str | None
|
_tmpdir: str | None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# pylint: disable=c-extension-no-member
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
to_json_bytes: Callable[[Any], bytes] = orjson.dumps
|
to_json_bytes: Callable[[Any], bytes] = orjson.dumps
|
||||||
|
@ -156,12 +155,12 @@ def field_checksum(data: str) -> int:
|
||||||
# Temp files
|
# Temp files
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
_tmpdir = None # pylint: disable=invalid-name
|
_tmpdir = None
|
||||||
|
|
||||||
|
|
||||||
def tmpdir() -> str:
|
def tmpdir() -> str:
|
||||||
"A reusable temp folder which we clean out on each program invocation."
|
"A reusable temp folder which we clean out on each program invocation."
|
||||||
global _tmpdir # pylint: disable=invalid-name
|
global _tmpdir
|
||||||
if not _tmpdir:
|
if not _tmpdir:
|
||||||
|
|
||||||
def cleanup() -> None:
|
def cleanup() -> None:
|
||||||
|
@ -216,7 +215,6 @@ def call(argv: list[str], wait: bool = True, **kwargs: Any) -> int:
|
||||||
try:
|
try:
|
||||||
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
|
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
|
||||||
except Exception:
|
except Exception:
|
||||||
# pylint: disable=no-member
|
|
||||||
info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore
|
info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore
|
||||||
else:
|
else:
|
||||||
info = None
|
info = None
|
||||||
|
@ -282,7 +280,7 @@ def plat_desc() -> str:
|
||||||
elif is_win:
|
elif is_win:
|
||||||
theos = f"win:{platform.win32_ver()[0]}"
|
theos = f"win:{platform.win32_ver()[0]}"
|
||||||
elif system == "Linux":
|
elif system == "Linux":
|
||||||
import distro # pytype: disable=import-error # pylint: disable=import-error
|
import distro # pytype: disable=import-error
|
||||||
|
|
||||||
dist_id = distro.id()
|
dist_id = distro.id()
|
||||||
dist_version = distro.version()
|
dist_version = distro.version()
|
||||||
|
|
|
@ -169,8 +169,7 @@ def test_find_cards():
|
||||||
# properties
|
# properties
|
||||||
id = col.db.scalar("select id from cards limit 1")
|
id = col.db.scalar("select id from cards limit 1")
|
||||||
col.db.execute(
|
col.db.execute(
|
||||||
"update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 "
|
"update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 where id = ?",
|
||||||
"where id = ?",
|
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
assert len(col.find_cards("prop:ivl>5")) == 1
|
assert len(col.find_cards("prop:ivl>5")) == 1
|
||||||
|
|
|
@ -551,12 +551,10 @@ def test_bury():
|
||||||
col.addNote(note)
|
col.addNote(note)
|
||||||
c2 = note.cards()[0]
|
c2 = note.cards()[0]
|
||||||
# burying
|
# burying
|
||||||
col.sched.bury_cards([c.id], manual=True) # pylint: disable=unexpected-keyword-arg
|
col.sched.bury_cards([c.id], manual=True)
|
||||||
c.load()
|
c.load()
|
||||||
assert c.queue == QUEUE_TYPE_MANUALLY_BURIED
|
assert c.queue == QUEUE_TYPE_MANUALLY_BURIED
|
||||||
col.sched.bury_cards(
|
col.sched.bury_cards([c2.id], manual=False)
|
||||||
[c2.id], manual=False
|
|
||||||
) # pylint: disable=unexpected-keyword-arg
|
|
||||||
c2.load()
|
c2.load()
|
||||||
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
|
assert c2.queue == QUEUE_TYPE_SIBLING_BURIED
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,5 @@ with open(buildhash_file, "r", encoding="utf8") as f:
|
||||||
|
|
||||||
with open(outpath, "w", encoding="utf8") as f:
|
with open(outpath, "w", encoding="utf8") as f:
|
||||||
# if we switch to uppercase we'll need to add legacy aliases
|
# 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"version = '{version}'\n")
|
||||||
f.write(f"buildhash = '{buildhash}'\n")
|
f.write(f"buildhash = '{buildhash}'\n")
|
||||||
|
|
|
@ -133,7 +133,7 @@ prefix = """\
|
||||||
# This file is automatically generated; edit tools/genhooks.py instead.
|
# This file is automatically generated; edit tools/genhooks.py instead.
|
||||||
# Please import from anki.hooks instead of this file.
|
# Please import from anki.hooks instead of this file.
|
||||||
|
|
||||||
# pylint: disable=unused-import
|
# ruff: noqa: F401
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ Code for generating hooks.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
@ -204,9 +203,6 @@ def write_file(path: str, hooks: list[Hook], prefix: str, suffix: str):
|
||||||
|
|
||||||
code += f"\n{suffix}"
|
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:
|
with open(path, "wb") as file:
|
||||||
file.write(code.encode("utf8"))
|
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)
|
||||||
|
|
|
@ -7,14 +7,11 @@ classifiers = ["Private :: Do Not Upload"]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"black",
|
|
||||||
"isort",
|
|
||||||
"mypy",
|
"mypy",
|
||||||
"mypy-protobuf",
|
"mypy-protobuf",
|
||||||
"pylint",
|
"ruff",
|
||||||
"pytest",
|
"pytest",
|
||||||
"PyChromeDevTools",
|
"PyChromeDevTools",
|
||||||
"colorama", # for isort --color
|
|
||||||
"wheel",
|
"wheel",
|
||||||
"hatchling", # for type checking hatch_build.py files
|
"hatchling", # for type checking hatch_build.py files
|
||||||
"mock",
|
"mock",
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
os.environ["REPO_ROOT"] = os.path.abspath(".")
|
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-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)
|
subprocess.run(["out/pyenv/bin/sphinx-build", "out/python/sphinx", "out/python/sphinx/html"], check=True)
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[settings]
|
|
||||||
py_version=39
|
|
||||||
profile=black
|
|
||||||
known_first_party=anki,aqt
|
|
||||||
extend_skip=aqt/forms,hooks_gen.py
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# ruff: noqa: F401
|
||||||
import atexit
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -28,7 +29,7 @@ if sys.version_info[0] < 3 or sys.version_info[1] < 9:
|
||||||
# ensure unicode filenames are supported
|
# ensure unicode filenames are supported
|
||||||
try:
|
try:
|
||||||
"テスト".encode(sys.getfilesystemencoding())
|
"テスト".encode(sys.getfilesystemencoding())
|
||||||
except UnicodeEncodeError as exc:
|
except UnicodeEncodeError:
|
||||||
print("Anki requires a UTF-8 locale.")
|
print("Anki requires a UTF-8 locale.")
|
||||||
print("Please Google 'how to change locale on [your Linux distro]'")
|
print("Please Google 'how to change locale on [your Linux distro]'")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -285,7 +286,6 @@ class NativeEventFilter(QAbstractNativeEventFilter):
|
||||||
def nativeEventFilter(
|
def nativeEventFilter(
|
||||||
self, eventType: Any, message: Any
|
self, eventType: Any, message: Any
|
||||||
) -> tuple[bool, Any | None]:
|
) -> tuple[bool, Any | None]:
|
||||||
|
|
||||||
if eventType == "windows_generic_MSG":
|
if eventType == "windows_generic_MSG":
|
||||||
import ctypes.wintypes
|
import ctypes.wintypes
|
||||||
|
|
||||||
|
@ -558,7 +558,7 @@ def run() -> None:
|
||||||
print(f"Starting Anki {_version}...")
|
print(f"Starting Anki {_version}...")
|
||||||
try:
|
try:
|
||||||
_run()
|
_run()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
QMessageBox.critical(
|
QMessageBox.critical(
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -6,8 +6,6 @@ from __future__ import annotations
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
from anki_mac_helper import ( # pylint:disable=unused-import,import-error
|
from anki_mac_helper import macos_helper
|
||||||
macos_helper,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
macos_helper = None
|
macos_helper = None
|
||||||
|
|
|
@ -927,7 +927,6 @@ class AddonsDialog(QDialog):
|
||||||
or self.mgr.configAction(addon.dir_name)
|
or self.mgr.configAction(addon.dir_name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
def _onAddonItemSelected(self, row_int: int) -> None:
|
def _onAddonItemSelected(self, row_int: int) -> None:
|
||||||
try:
|
try:
|
||||||
|
@ -1457,7 +1456,9 @@ class ChooseAddonsToUpdateDialog(QDialog):
|
||||||
layout.addWidget(addons_list_widget)
|
layout.addWidget(addons_list_widget)
|
||||||
self.addons_list_widget = 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(
|
qconnect(
|
||||||
button_box.button(QDialogButtonBox.StandardButton.Ok).clicked, self.accept
|
button_box.button(QDialogButtonBox.StandardButton.Ok).clicked, self.accept
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,7 +36,6 @@ def ankihub_login(
|
||||||
username: str = "",
|
username: str = "",
|
||||||
password: str = "",
|
password: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
def on_future_done(fut: Future[str], username: str, password: str) -> None:
|
def on_future_done(fut: Future[str], username: str, password: str) -> None:
|
||||||
try:
|
try:
|
||||||
token = fut.result()
|
token = fut.result()
|
||||||
|
@ -73,7 +72,6 @@ def ankihub_logout(
|
||||||
on_success: Callable[[], None],
|
on_success: Callable[[], None],
|
||||||
token: str,
|
token: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
def logout() -> None:
|
def logout() -> None:
|
||||||
mw.pm.set_ankihub_username(None)
|
mw.pm.set_ankihub_username(None)
|
||||||
mw.pm.set_ankihub_token(None)
|
mw.pm.set_ankihub_token(None)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# ruff: noqa: F401
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
# ruff: noqa: F401
|
||||||
from anki.utils import is_mac
|
from anki.utils import is_mac
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ class SidebarTreeView(QTreeView):
|
||||||
def _setup_style(self) -> None:
|
def _setup_style(self) -> None:
|
||||||
# match window background color and tweak style
|
# match window background color and tweak style
|
||||||
bgcolor = QPalette().window().color().name()
|
bgcolor = QPalette().window().color().name()
|
||||||
border = theme_manager.var(colors.BORDER)
|
theme_manager.var(colors.BORDER)
|
||||||
styles = [
|
styles = [
|
||||||
"padding: 3px",
|
"padding: 3px",
|
||||||
"padding-right: 0px",
|
"padding-right: 0px",
|
||||||
|
@ -711,7 +711,6 @@ class SidebarTreeView(QTreeView):
|
||||||
|
|
||||||
def _flags_tree(self, root: SidebarItem) -> None:
|
def _flags_tree(self, root: SidebarItem) -> None:
|
||||||
icon_off = "icons:flag-variant-off-outline.svg"
|
icon_off = "icons:flag-variant-off-outline.svg"
|
||||||
icon = "icons:flag-variant.svg"
|
|
||||||
icon_outline = "icons:flag-variant-outline.svg"
|
icon_outline = "icons:flag-variant-outline.svg"
|
||||||
|
|
||||||
root = self._section_root(
|
root = self._section_root(
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# ruff: noqa: F401
|
||||||
import copy
|
import copy
|
||||||
import time
|
import time
|
||||||
from collections.abc import Generator, Sequence
|
from collections.abc import Generator, Sequence
|
||||||
|
|
|
@ -105,11 +105,11 @@ class DataModel(QAbstractTableModel):
|
||||||
row = CellRow(*self.col.browser_row_for_id(item))
|
row = CellRow(*self.col.browser_row_for_id(item))
|
||||||
except BackendError as e:
|
except BackendError as e:
|
||||||
return CellRow.disabled(self.len_columns(), str(e))
|
return CellRow.disabled(self.len_columns(), str(e))
|
||||||
except Exception as e:
|
except Exception:
|
||||||
return CellRow.disabled(
|
return CellRow.disabled(
|
||||||
self.len_columns(), tr.errors_please_check_database()
|
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
|
# fatal error like a panic in the backend - dump it to the
|
||||||
# console so it gets picked up by the error handler
|
# console so it gets picked up by the error handler
|
||||||
import traceback
|
import traceback
|
||||||
|
|
|
@ -59,7 +59,7 @@ class ItemState(ABC):
|
||||||
|
|
||||||
# abstractproperty is deprecated but used due to mypy limitations
|
# abstractproperty is deprecated but used due to mypy limitations
|
||||||
# (https://github.com/python/mypy/issues/1362)
|
# (https://github.com/python/mypy/issues/1362)
|
||||||
@abstractproperty # pylint: disable=deprecated-decorator
|
@abstractproperty
|
||||||
def active_columns(self) -> list[str]:
|
def active_columns(self) -> list[str]:
|
||||||
"""Return the saved or default columns for the state."""
|
"""Return the saved or default columns for the state."""
|
||||||
|
|
||||||
|
|
|
@ -361,8 +361,7 @@ class Table:
|
||||||
for m in self.col.models.all():
|
for m in self.col.models.all():
|
||||||
for t in m["tmpls"]:
|
for t in m["tmpls"]:
|
||||||
bsize = t.get("bsize", 0)
|
bsize = t.get("bsize", 0)
|
||||||
if bsize > curmax:
|
curmax = max(curmax, bsize)
|
||||||
curmax = bsize
|
|
||||||
|
|
||||||
assert self._view is not None
|
assert self._view is not None
|
||||||
vh = self._view.verticalHeader()
|
vh = self._view.verticalHeader()
|
||||||
|
|
|
@ -221,7 +221,7 @@ class CardLayout(QDialog):
|
||||||
)
|
)
|
||||||
for i in range(min(len(self.cloze_numbers), 9)):
|
for i in range(min(len(self.cloze_numbers), 9)):
|
||||||
QShortcut( # type: ignore
|
QShortcut( # type: ignore
|
||||||
QKeySequence(f"Alt+{i+1}"),
|
QKeySequence(f"Alt+{i + 1}"),
|
||||||
self,
|
self,
|
||||||
activated=lambda n=i: self.pform.cloze_number_combo.setCurrentIndex(n),
|
activated=lambda n=i: self.pform.cloze_number_combo.setCurrentIndex(n),
|
||||||
)
|
)
|
||||||
|
@ -790,7 +790,7 @@ class CardLayout(QDialog):
|
||||||
assert a is not None
|
assert a is not None
|
||||||
qconnect(
|
qconnect(
|
||||||
a.triggered,
|
a.triggered,
|
||||||
lambda: self.on_restore_to_default(), # pylint: disable=unnecessary-lambda
|
lambda: self.on_restore_to_default(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self._isCloze():
|
if not self._isCloze():
|
||||||
|
|
|
@ -294,7 +294,6 @@ class DebugConsole(QDialog):
|
||||||
}
|
}
|
||||||
self._captureOutput(True)
|
self._captureOutput(True)
|
||||||
try:
|
try:
|
||||||
# pylint: disable=exec-used
|
|
||||||
exec(text, vars)
|
exec(text, vars)
|
||||||
except Exception:
|
except Exception:
|
||||||
self._output += traceback.format_exc()
|
self._output += traceback.format_exc()
|
||||||
|
|
|
@ -386,9 +386,7 @@ class DeckBrowser:
|
||||||
if b[0]:
|
if b[0]:
|
||||||
b[0] = tr.actions_shortcut_key(val=shortcut(b[0]))
|
b[0] = tr.actions_shortcut_key(val=shortcut(b[0]))
|
||||||
buf += """
|
buf += """
|
||||||
<button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(
|
<button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b)
|
||||||
b
|
|
||||||
)
|
|
||||||
self.bottom.draw(
|
self.bottom.draw(
|
||||||
buf=buf,
|
buf=buf,
|
||||||
link_handler=self._linkHandler,
|
link_handler=self._linkHandler,
|
||||||
|
|
|
@ -343,7 +343,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
gui_hooks.editor_did_init_shortcuts(cuts, self)
|
gui_hooks.editor_did_init_shortcuts(cuts, self)
|
||||||
for row in cuts:
|
for row in cuts:
|
||||||
if len(row) == 2:
|
if len(row) == 2:
|
||||||
keys, fn = row # pylint: disable=unbalanced-tuple-unpacking
|
keys, fn = row
|
||||||
fn = self._addFocusCheck(fn)
|
fn = self._addFocusCheck(fn)
|
||||||
else:
|
else:
|
||||||
keys, fn, _ = row
|
keys, fn, _ = row
|
||||||
|
@ -796,7 +796,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
def accept(file: str) -> None:
|
def accept(file: str) -> None:
|
||||||
self.resolve_media(file)
|
self.resolve_media(file)
|
||||||
|
|
||||||
file = getFile(
|
getFile(
|
||||||
parent=self.widget,
|
parent=self.widget,
|
||||||
title=tr.editing_add_media(),
|
title=tr.editing_add_media(),
|
||||||
cb=cast(Callable[[Any], None], accept),
|
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:
|
if html.find(">") < 0:
|
||||||
return html
|
return html
|
||||||
|
|
||||||
with warnings.catch_warnings() as w:
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore", UserWarning)
|
warnings.simplefilter("ignore", UserWarning)
|
||||||
doc = BeautifulSoup(html, "html.parser")
|
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))
|
m = re.match(r"http://127.0.0.1:\d+/(.*)$", str(src))
|
||||||
if m:
|
if m:
|
||||||
tag["src"] = m.group(1)
|
tag["src"] = m.group(1)
|
||||||
else:
|
# in external pastes, download remote media
|
||||||
# in external pastes, download remote media
|
elif isinstance(src, str) and self.isURL(src):
|
||||||
if isinstance(src, str) and self.isURL(src):
|
fname = self._retrieveURL(src)
|
||||||
fname = self._retrieveURL(src)
|
if fname:
|
||||||
if fname:
|
tag["src"] = fname
|
||||||
tag["src"] = fname
|
elif isinstance(src, str) and src.startswith("data:image/"):
|
||||||
elif isinstance(src, str) and src.startswith("data:image/"):
|
# and convert inlined data
|
||||||
# and convert inlined data
|
tag["src"] = self.inlinedImageToFilename(str(src))
|
||||||
tag["src"] = self.inlinedImageToFilename(str(src))
|
|
||||||
|
|
||||||
html = str(doc)
|
html = str(doc)
|
||||||
return html
|
return html
|
||||||
|
@ -1102,7 +1101,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
)
|
)
|
||||||
filter = f"{tr.editing_media()} ({extension_filter})"
|
filter = f"{tr.editing_media()} ({extension_filter})"
|
||||||
|
|
||||||
file = getFile(
|
getFile(
|
||||||
parent=self.widget,
|
parent=self.widget,
|
||||||
title=tr.editing_add_media(),
|
title=tr.editing_add_media(),
|
||||||
cb=cast(Callable[[Any], None], self.setup_mask_editor),
|
cb=cast(Callable[[Any], None], self.setup_mask_editor),
|
||||||
|
|
|
@ -212,11 +212,10 @@ class ExportDialog(QDialog):
|
||||||
if self.isVerbatim:
|
if self.isVerbatim:
|
||||||
msg = tr.exporting_collection_exported()
|
msg = tr.exporting_collection_exported()
|
||||||
self.mw.reopen()
|
self.mw.reopen()
|
||||||
|
elif self.isTextNote:
|
||||||
|
msg = tr.exporting_note_exported(count=self.exporter.count)
|
||||||
else:
|
else:
|
||||||
if self.isTextNote:
|
msg = tr.exporting_card_exported(count=self.exporter.count)
|
||||||
msg = tr.exporting_note_exported(count=self.exporter.count)
|
|
||||||
else:
|
|
||||||
msg = tr.exporting_card_exported(count=self.exporter.count)
|
|
||||||
gui_hooks.legacy_exporter_did_export(self.exporter)
|
gui_hooks.legacy_exporter_did_export(self.exporter)
|
||||||
tooltip(msg, period=3000)
|
tooltip(msg, period=3000)
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# ruff: noqa: F401
|
||||||
from . import (
|
from . import (
|
||||||
about,
|
about,
|
||||||
addcards,
|
addcards,
|
||||||
|
|
|
@ -134,9 +134,8 @@ IMPORTERS: list[type[Importer]] = [
|
||||||
|
|
||||||
|
|
||||||
def legacy_file_endings(col: Collection) -> list[str]:
|
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 MnemosyneImporter as LegacyMnemosyneImporter
|
||||||
from anki.importing import TextImporter, importers
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
ext
|
ext
|
||||||
|
|
|
@ -11,10 +11,10 @@ from collections.abc import Callable
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import anki.importing as importing
|
|
||||||
import aqt.deckchooser
|
import aqt.deckchooser
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
import aqt.modelchooser
|
import aqt.modelchooser
|
||||||
|
from anki import importing
|
||||||
from anki.importing.anki2 import MediaMapInvalid, V2ImportIntoV1
|
from anki.importing.anki2 import MediaMapInvalid, V2ImportIntoV1
|
||||||
from anki.importing.apkg import AnkiPackageImporter
|
from anki.importing.apkg import AnkiPackageImporter
|
||||||
from aqt.import_export.importing import ColpkgImporter
|
from aqt.import_export.importing import ColpkgImporter
|
||||||
|
@ -262,7 +262,7 @@ class ImportDialog(QDialog):
|
||||||
self.mapwidget.setLayout(self.grid)
|
self.mapwidget.setLayout(self.grid)
|
||||||
self.grid.setContentsMargins(3, 3, 3, 3)
|
self.grid.setContentsMargins(3, 3, 3, 3)
|
||||||
self.grid.setSpacing(6)
|
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)
|
text = tr.importing_field_of_file_is(val=num + 1)
|
||||||
self.grid.addWidget(QLabel(text), num, 0)
|
self.grid.addWidget(QLabel(text), num, 0)
|
||||||
if self.mapping[num] == "_tags":
|
if self.mapping[num] == "_tags":
|
||||||
|
@ -357,7 +357,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
|
||||||
try:
|
try:
|
||||||
importer.open()
|
importer.open()
|
||||||
mw.progress.finish()
|
mw.progress.finish()
|
||||||
diag = ImportDialog(mw, importer)
|
ImportDialog(mw, importer)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
mw.progress.finish()
|
mw.progress.finish()
|
||||||
showUnicodeWarning()
|
showUnicodeWarning()
|
||||||
|
@ -443,3 +443,4 @@ def setupApkgImport(mw: AnkiQt, importer: AnkiPackageImporter) -> bool:
|
||||||
return True
|
return True
|
||||||
ColpkgImporter.do_import(mw, importer.file)
|
ColpkgImporter.do_import(mw, importer.file)
|
||||||
return False
|
return False
|
||||||
|
return False
|
||||||
|
|
|
@ -376,7 +376,6 @@ class AnkiQt(QMainWindow):
|
||||||
def openProfile(self) -> None:
|
def openProfile(self) -> None:
|
||||||
name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
|
name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
|
||||||
self.pm.load(name)
|
self.pm.load(name)
|
||||||
return
|
|
||||||
|
|
||||||
def onOpenProfile(self, *, callback: Callable[[], None] | None = None) -> None:
|
def onOpenProfile(self, *, callback: Callable[[], None] | None = None) -> None:
|
||||||
def on_done() -> None:
|
def on_done() -> None:
|
||||||
|
@ -451,7 +450,6 @@ class AnkiQt(QMainWindow):
|
||||||
self.loadProfile()
|
self.loadProfile()
|
||||||
|
|
||||||
def onOpenBackup(self) -> None:
|
def onOpenBackup(self) -> None:
|
||||||
|
|
||||||
def do_open(path: str) -> None:
|
def do_open(path: str) -> None:
|
||||||
if not askUser(
|
if not askUser(
|
||||||
tr.qt_misc_replace_your_collection_with_an_earlier2(
|
tr.qt_misc_replace_your_collection_with_an_earlier2(
|
||||||
|
@ -677,7 +675,7 @@ class AnkiQt(QMainWindow):
|
||||||
gui_hooks.collection_did_load(self.col)
|
gui_hooks.collection_did_load(self.col)
|
||||||
self.apply_collection_options()
|
self.apply_collection_options()
|
||||||
self.moveToState("deckBrowser")
|
self.moveToState("deckBrowser")
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# dump error to stderr so it gets picked up by errors.py
|
# dump error to stderr so it gets picked up by errors.py
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
@ -774,7 +772,6 @@ class AnkiQt(QMainWindow):
|
||||||
oldState = self.state
|
oldState = self.state
|
||||||
cleanup = getattr(self, f"_{oldState}Cleanup", None)
|
cleanup = getattr(self, f"_{oldState}Cleanup", None)
|
||||||
if cleanup:
|
if cleanup:
|
||||||
# pylint: disable=not-callable
|
|
||||||
cleanup(state)
|
cleanup(state)
|
||||||
self.clearStateShortcuts()
|
self.clearStateShortcuts()
|
||||||
self.state = state
|
self.state = state
|
||||||
|
@ -821,7 +818,7 @@ class AnkiQt(QMainWindow):
|
||||||
self.bottomWeb.hide_timer.start()
|
self.bottomWeb.hide_timer.start()
|
||||||
|
|
||||||
def _reviewCleanup(self, newState: MainWindowState) -> None:
|
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.auto_advance_enabled = False
|
||||||
self.reviewer.cleanup()
|
self.reviewer.cleanup()
|
||||||
self.toolbarWeb.elevate()
|
self.toolbarWeb.elevate()
|
||||||
|
|
|
@ -230,7 +230,11 @@ def _handle_local_file_request(request: LocalFileRequest) -> Response:
|
||||||
else:
|
else:
|
||||||
max_age = 60 * 60
|
max_age = 60 * 60
|
||||||
return flask.send_file(
|
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:
|
else:
|
||||||
print(f"Not found: {path}")
|
print(f"Not found: {path}")
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#
|
#
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
# pylint: disable=raise-missing-from
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -66,7 +66,6 @@ class MPVTimeoutError(MPVError):
|
||||||
|
|
||||||
|
|
||||||
if is_win:
|
if is_win:
|
||||||
# pylint: disable=import-error
|
|
||||||
import pywintypes
|
import pywintypes
|
||||||
import win32file # pytype: disable=import-error
|
import win32file # pytype: disable=import-error
|
||||||
import win32job
|
import win32job
|
||||||
|
@ -138,15 +137,15 @@ class MPVBase:
|
||||||
extended_info = win32job.QueryInformationJobObject(
|
extended_info = win32job.QueryInformationJobObject(
|
||||||
self._job, win32job.JobObjectExtendedLimitInformation
|
self._job, win32job.JobObjectExtendedLimitInformation
|
||||||
)
|
)
|
||||||
extended_info["BasicLimitInformation"][
|
extended_info["BasicLimitInformation"]["LimitFlags"] = (
|
||||||
"LimitFlags"
|
win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
||||||
] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
)
|
||||||
win32job.SetInformationJobObject(
|
win32job.SetInformationJobObject(
|
||||||
self._job,
|
self._job,
|
||||||
win32job.JobObjectExtendedLimitInformation,
|
win32job.JobObjectExtendedLimitInformation,
|
||||||
extended_info,
|
extended_info,
|
||||||
)
|
)
|
||||||
handle = self._proc._handle # pylint: disable=no-member
|
handle = self._proc._handle
|
||||||
win32job.AssignProcessToJobObject(self._job, handle)
|
win32job.AssignProcessToJobObject(self._job, handle)
|
||||||
|
|
||||||
def _stop_process(self):
|
def _stop_process(self):
|
||||||
|
@ -193,7 +192,10 @@ class MPVBase:
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
win32pipe.SetNamedPipeHandleState(
|
win32pipe.SetNamedPipeHandleState(
|
||||||
self._sock, 1, None, None # PIPE_NOWAIT
|
self._sock,
|
||||||
|
1,
|
||||||
|
None,
|
||||||
|
None, # PIPE_NOWAIT
|
||||||
)
|
)
|
||||||
except pywintypes.error as err:
|
except pywintypes.error as err:
|
||||||
if err.args[0] == winerror.ERROR_FILE_NOT_FOUND:
|
if err.args[0] == winerror.ERROR_FILE_NOT_FOUND:
|
||||||
|
@ -394,7 +396,7 @@ class MPVBase:
|
||||||
return self._get_response(timeout)
|
return self._get_response(timeout)
|
||||||
except MPVCommandError as e:
|
except MPVCommandError as e:
|
||||||
raise MPVCommandError(f"{message['command']!r}: {e}")
|
raise MPVCommandError(f"{message['command']!r}: {e}")
|
||||||
except Exception as e:
|
except Exception:
|
||||||
if _retry:
|
if _retry:
|
||||||
print("mpv timed out, restarting")
|
print("mpv timed out, restarting")
|
||||||
self._stop_process()
|
self._stop_process()
|
||||||
|
@ -501,7 +503,6 @@ class MPV(MPVBase):
|
||||||
# Simulate an init event when the process and all callbacks have been
|
# Simulate an init event when the process and all callbacks have been
|
||||||
# completely set up.
|
# completely set up.
|
||||||
if hasattr(self, "on_init"):
|
if hasattr(self, "on_init"):
|
||||||
# pylint: disable=no-member
|
|
||||||
self.on_init()
|
self.on_init()
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -113,7 +113,7 @@ class Overview:
|
||||||
self.mw.moveToState("deckBrowser")
|
self.mw.moveToState("deckBrowser")
|
||||||
elif url == "review":
|
elif url == "review":
|
||||||
openLink(f"{aqt.appShared}info/{self.sid}?v={self.sidVer}")
|
openLink(f"{aqt.appShared}info/{self.sid}?v={self.sidVer}")
|
||||||
elif url == "studymore" or url == "customStudy":
|
elif url in {"studymore", "customStudy"}:
|
||||||
self.onStudyMore()
|
self.onStudyMore()
|
||||||
elif url == "unbury":
|
elif url == "unbury":
|
||||||
self.on_unbury()
|
self.on_unbury()
|
||||||
|
@ -180,7 +180,6 @@ class Overview:
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
def _renderPage(self) -> None:
|
def _renderPage(self) -> None:
|
||||||
but = self.mw.button
|
|
||||||
deck = self.mw.col.decks.current()
|
deck = self.mw.col.decks.current()
|
||||||
self.sid = deck.get("sharedFrom")
|
self.sid = deck.get("sharedFrom")
|
||||||
if self.sid:
|
if self.sid:
|
||||||
|
@ -307,9 +306,7 @@ class Overview:
|
||||||
if b[0]:
|
if b[0]:
|
||||||
b[0] = tr.actions_shortcut_key(val=shortcut(b[0]))
|
b[0] = tr.actions_shortcut_key(val=shortcut(b[0]))
|
||||||
buf += """
|
buf += """
|
||||||
<button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(
|
<button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(b)
|
||||||
b
|
|
||||||
)
|
|
||||||
self.bottom.draw(
|
self.bottom.draw(
|
||||||
buf=buf,
|
buf=buf,
|
||||||
link_handler=link_handler,
|
link_handler=link_handler,
|
||||||
|
|
|
@ -11,7 +11,7 @@ from pathlib import Path
|
||||||
from anki.utils import is_mac
|
from anki.utils import is_mac
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-import,import-error
|
# ruff: noqa: F401
|
||||||
def first_run_setup() -> None:
|
def first_run_setup() -> None:
|
||||||
"""Code run the first time after install/upgrade.
|
"""Code run the first time after install/upgrade.
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ class ProfileManager:
|
||||||
default_answer_keys = {ease_num: str(ease_num) for ease_num in range(1, 5)}
|
default_answer_keys = {ease_num: str(ease_num) for ease_num in range(1, 5)}
|
||||||
last_run_version: int = 0
|
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"
|
"base should be retrieved via ProfileMangager.get_created_base_folder"
|
||||||
## Settings which should be forgotten each Anki restart
|
## Settings which should be forgotten each Anki restart
|
||||||
self.session: dict[str, Any] = {}
|
self.session: dict[str, Any] = {}
|
||||||
|
@ -153,7 +153,7 @@ class ProfileManager:
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.load(profile)
|
self.load(profile)
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
self.invalid_profile_provided_on_commandline = True
|
self.invalid_profile_provided_on_commandline = True
|
||||||
|
|
||||||
# Profile load/save
|
# Profile load/save
|
||||||
|
@ -483,7 +483,11 @@ create table if not exists profiles
|
||||||
code = obj[1]
|
code = obj[1]
|
||||||
name = obj[0]
|
name = obj[0]
|
||||||
r = QMessageBox.question(
|
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:
|
if r != QMessageBox.StandardButton.Yes:
|
||||||
return self.setDefaultLang(f.lang.currentRow())
|
return self.setDefaultLang(f.lang.currentRow())
|
||||||
|
|
|
@ -119,13 +119,12 @@ class ProgressManager:
|
||||||
if not self._levels:
|
if not self._levels:
|
||||||
# no current progress; safe to fire
|
# no current progress; safe to fire
|
||||||
func()
|
func()
|
||||||
|
elif repeat:
|
||||||
|
# skip this time; we'll fire again
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
if repeat:
|
# retry in 100ms
|
||||||
# skip this time; we'll fire again
|
self.single_shot(100, func, requires_collection)
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# retry in 100ms
|
|
||||||
self.single_shot(100, func, requires_collection)
|
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# make sure not to optimize imports on this file
|
# make sure not to optimize imports on this file
|
||||||
# pylint: disable=unused-import
|
# ruff: noqa: F401
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -23,7 +23,7 @@ def debug() -> None:
|
||||||
from pdb import set_trace
|
from pdb import set_trace
|
||||||
|
|
||||||
pyqtRemoveInputHook()
|
pyqtRemoveInputHook()
|
||||||
set_trace() # pylint: disable=forgotten-debug-statement
|
set_trace()
|
||||||
|
|
||||||
|
|
||||||
if os.environ.get("DEBUG"):
|
if os.environ.get("DEBUG"):
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
# make sure not to optimize imports on this file
|
# make sure not to optimize imports on this file
|
||||||
# pylint: disable=unused-import
|
# ruff: noqa: F401
|
||||||
|
|
||||||
"""
|
"""
|
||||||
PyQt6 imports
|
PyQt6 imports
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,13 +21,11 @@ from anki.scheduler.base import ScheduleCardsAsNew
|
||||||
from anki.scheduler.v3 import (
|
from anki.scheduler.v3 import (
|
||||||
CardAnswer,
|
CardAnswer,
|
||||||
QueuedCards,
|
QueuedCards,
|
||||||
)
|
|
||||||
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
|
||||||
from anki.scheduler.v3 import (
|
|
||||||
SchedulingContext,
|
SchedulingContext,
|
||||||
SchedulingStates,
|
SchedulingStates,
|
||||||
SetSchedulingStatesRequest,
|
SetSchedulingStatesRequest,
|
||||||
)
|
)
|
||||||
|
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
||||||
from anki.tags import MARKED_TAG
|
from anki.tags import MARKED_TAG
|
||||||
from anki.types import assert_exhaustive
|
from anki.types import assert_exhaustive
|
||||||
from anki.utils import is_mac
|
from anki.utils import is_mac
|
||||||
|
@ -597,10 +595,9 @@ class Reviewer:
|
||||||
def _shortcutKeys(
|
def _shortcutKeys(
|
||||||
self,
|
self,
|
||||||
) -> Sequence[tuple[str, Callable] | tuple[Qt.Key, Callable]]:
|
) -> Sequence[tuple[str, Callable] | tuple[Qt.Key, Callable]]:
|
||||||
|
def generate_default_answer_keys() -> Generator[
|
||||||
def generate_default_answer_keys() -> (
|
tuple[str, partial], None, None
|
||||||
Generator[tuple[str, partial], None, None]
|
]:
|
||||||
):
|
|
||||||
for ease in aqt.mw.pm.default_answer_keys:
|
for ease in aqt.mw.pm.default_answer_keys:
|
||||||
key = aqt.mw.pm.get_answer_key(ease)
|
key = aqt.mw.pm.get_answer_key(ease)
|
||||||
if not key:
|
if not key:
|
||||||
|
|
|
@ -101,7 +101,7 @@ def is_audio_file(fname: str) -> bool:
|
||||||
return ext in AUDIO_EXTENSIONS
|
return ext in AUDIO_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
class SoundOrVideoPlayer(Player): # pylint: disable=abstract-method
|
class SoundOrVideoPlayer(Player):
|
||||||
default_rank = 0
|
default_rank = 0
|
||||||
|
|
||||||
def rank_for_tag(self, tag: AVTag) -> int | None:
|
def rank_for_tag(self, tag: AVTag) -> int | None:
|
||||||
|
@ -111,7 +111,7 @@ class SoundOrVideoPlayer(Player): # pylint: disable=abstract-method
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SoundPlayer(Player): # pylint: disable=abstract-method
|
class SoundPlayer(Player):
|
||||||
default_rank = 0
|
default_rank = 0
|
||||||
|
|
||||||
def rank_for_tag(self, tag: AVTag) -> int | None:
|
def rank_for_tag(self, tag: AVTag) -> int | None:
|
||||||
|
@ -121,7 +121,7 @@ class SoundPlayer(Player): # pylint: disable=abstract-method
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class VideoPlayer(Player): # pylint: disable=abstract-method
|
class VideoPlayer(Player):
|
||||||
default_rank = 0
|
default_rank = 0
|
||||||
|
|
||||||
def rank_for_tag(self, tag: AVTag) -> int | None:
|
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."
|
"A player that invokes a new process for each tag to play."
|
||||||
|
|
||||||
args: list[str] = []
|
args: list[str] = []
|
||||||
|
|
|
@ -208,7 +208,7 @@ class CustomStyles:
|
||||||
button_pressed_gradient(
|
button_pressed_gradient(
|
||||||
tm.var(colors.BUTTON_GRADIENT_START),
|
tm.var(colors.BUTTON_GRADIENT_START),
|
||||||
tm.var(colors.BUTTON_GRADIENT_END),
|
tm.var(colors.BUTTON_GRADIENT_END),
|
||||||
tm.var(colors.SHADOW)
|
tm.var(colors.SHADOW),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
@ -340,7 +340,7 @@ class CustomStyles:
|
||||||
}}
|
}}
|
||||||
QTabBar::tab:selected:hover {{
|
QTabBar::tab:selected:hover {{
|
||||||
background: {
|
background: {
|
||||||
button_gradient(
|
button_gradient(
|
||||||
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START),
|
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START),
|
||||||
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END),
|
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END),
|
||||||
)
|
)
|
||||||
|
@ -391,7 +391,7 @@ class CustomStyles:
|
||||||
button_pressed_gradient(
|
button_pressed_gradient(
|
||||||
tm.var(colors.BUTTON_GRADIENT_START),
|
tm.var(colors.BUTTON_GRADIENT_START),
|
||||||
tm.var(colors.BUTTON_GRADIENT_END),
|
tm.var(colors.BUTTON_GRADIENT_END),
|
||||||
tm.var(colors.SHADOW)
|
tm.var(colors.SHADOW),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -647,10 +647,12 @@ class CustomStyles:
|
||||||
margin: -7px 0;
|
margin: -7px 0;
|
||||||
}}
|
}}
|
||||||
QSlider::handle:hover {{
|
QSlider::handle:hover {{
|
||||||
background: {button_gradient(
|
background: {
|
||||||
tm.var(colors.BUTTON_GRADIENT_START),
|
button_gradient(
|
||||||
tm.var(colors.BUTTON_GRADIENT_END),
|
tm.var(colors.BUTTON_GRADIENT_START),
|
||||||
)}
|
tm.var(colors.BUTTON_GRADIENT_END),
|
||||||
|
)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ def get_sync_status(
|
||||||
) -> None:
|
) -> None:
|
||||||
auth = mw.pm.sync_auth()
|
auth = mw.pm.sync_auth()
|
||||||
if not auth:
|
if not auth:
|
||||||
callback(SyncStatus(required=SyncStatus.NO_CHANGES)) # pylint:disable=no-member
|
callback(SyncStatus(required=SyncStatus.NO_CHANGES))
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_future_done(fut: Future[SyncStatus]) -> None:
|
def on_future_done(fut: Future[SyncStatus]) -> None:
|
||||||
|
@ -302,7 +302,6 @@ def sync_login(
|
||||||
username: str = "",
|
username: str = "",
|
||||||
password: str = "",
|
password: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
def on_future_done(fut: Future[SyncAuth], username: str, password: str) -> None:
|
def on_future_done(fut: Future[SyncAuth], username: str, password: str) -> None:
|
||||||
try:
|
try:
|
||||||
auth = fut.result()
|
auth = fut.result()
|
||||||
|
@ -374,7 +373,9 @@ def get_id_and_pass_from_user(
|
||||||
g.addWidget(passwd, 1, 1)
|
g.addWidget(passwd, 1, 1)
|
||||||
l2.setBuddy(passwd)
|
l2.setBuddy(passwd)
|
||||||
vbox.addLayout(g)
|
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)
|
ok_button = bb.button(QDialogButtonBox.StandardButton.Ok)
|
||||||
assert ok_button is not None
|
assert ok_button is not None
|
||||||
ok_button.setAutoDefault(True)
|
ok_button.setAutoDefault(True)
|
||||||
|
|
|
@ -187,7 +187,7 @@ class ThemeManager:
|
||||||
self, card_ord: int, night_mode: bool | None = None
|
self, card_ord: int, night_mode: bool | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"Returns body classes used when showing a card."
|
"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:
|
def var(self, vars: dict[str, str]) -> str:
|
||||||
"""Given day/night colors/props, return the correct one for the current theme."""
|
"""Given day/night colors/props, return the correct one for the current theme."""
|
||||||
|
@ -213,13 +213,12 @@ class ThemeManager:
|
||||||
return False
|
return False
|
||||||
elif theme == Theme.DARK:
|
elif theme == Theme.DARK:
|
||||||
return True
|
return True
|
||||||
|
elif is_win:
|
||||||
|
return get_windows_dark_mode()
|
||||||
|
elif is_mac:
|
||||||
|
return get_macos_dark_mode()
|
||||||
else:
|
else:
|
||||||
if is_win:
|
return get_linux_dark_mode()
|
||||||
return get_windows_dark_mode()
|
|
||||||
elif is_mac:
|
|
||||||
return get_macos_dark_mode()
|
|
||||||
else:
|
|
||||||
return get_linux_dark_mode()
|
|
||||||
|
|
||||||
def apply_style(self) -> None:
|
def apply_style(self) -> None:
|
||||||
"Apply currently configured style."
|
"Apply currently configured style."
|
||||||
|
@ -340,7 +339,7 @@ def get_windows_dark_mode() -> bool:
|
||||||
if not is_win:
|
if not is_win:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from winreg import ( # type: ignore[attr-defined] # pylint: disable=import-error
|
from winreg import ( # type: ignore[attr-defined]
|
||||||
HKEY_CURRENT_USER,
|
HKEY_CURRENT_USER,
|
||||||
OpenKey,
|
OpenKey,
|
||||||
QueryValueEx,
|
QueryValueEx,
|
||||||
|
@ -352,7 +351,7 @@ def get_windows_dark_mode() -> bool:
|
||||||
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
||||||
)
|
)
|
||||||
return not QueryValueEx(key, "AppsUseLightTheme")[0]
|
return not QueryValueEx(key, "AppsUseLightTheme")[0]
|
||||||
except Exception as err:
|
except Exception:
|
||||||
# key reportedly missing or set to wrong type on some systems
|
# key reportedly missing or set to wrong type on some systems
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -416,12 +415,12 @@ def get_linux_dark_mode() -> bool:
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
encoding="utf8",
|
encoding="utf8",
|
||||||
)
|
)
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError:
|
||||||
# detection strategy failed, missing program
|
# detection strategy failed, missing program
|
||||||
# print(e)
|
# print(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError:
|
||||||
# detection strategy failed, command returned error
|
# detection strategy failed, command returned error
|
||||||
# print(e)
|
# print(e)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -166,7 +166,6 @@ class MacVoice(TTSVoice):
|
||||||
original_name: str
|
original_name: str
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-member
|
|
||||||
class MacTTSPlayer(TTSProcessPlayer):
|
class MacTTSPlayer(TTSProcessPlayer):
|
||||||
"Invokes a process to play the audio in the background."
|
"Invokes a process to play the audio in the background."
|
||||||
|
|
||||||
|
@ -487,7 +486,7 @@ if is_win:
|
||||||
class WindowsTTSPlayer(TTSProcessPlayer):
|
class WindowsTTSPlayer(TTSProcessPlayer):
|
||||||
default_rank = -1
|
default_rank = -1
|
||||||
try:
|
try:
|
||||||
import win32com.client # pylint: disable=import-error
|
import win32com.client
|
||||||
|
|
||||||
speaker = win32com.client.Dispatch("SAPI.SpVoice")
|
speaker = win32com.client.Dispatch("SAPI.SpVoice")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
@ -19,7 +19,7 @@ from send2trash import send2trash
|
||||||
import aqt
|
import aqt
|
||||||
from anki._legacy import DeprecatedNamesMixinForModule
|
from anki._legacy import DeprecatedNamesMixinForModule
|
||||||
from anki.collection import Collection, HelpPage
|
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 (
|
from anki.utils import (
|
||||||
call,
|
call,
|
||||||
invalid_filename,
|
invalid_filename,
|
||||||
|
@ -31,7 +31,7 @@ from anki.utils import (
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.qt import (
|
from aqt.qt import (
|
||||||
PYQT_VERSION_STR,
|
PYQT_VERSION_STR,
|
||||||
QT_VERSION_STR,
|
QT_VERSION_STR, # noqa: F401
|
||||||
QAction,
|
QAction,
|
||||||
QApplication,
|
QApplication,
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
|
@ -294,7 +294,7 @@ def showInfo(
|
||||||
icon = QMessageBox.Icon.Critical
|
icon = QMessageBox.Icon.Critical
|
||||||
else:
|
else:
|
||||||
icon = QMessageBox.Icon.Information
|
icon = QMessageBox.Icon.Information
|
||||||
mb = QMessageBox(parent_widget) #
|
mb = QMessageBox(parent_widget)
|
||||||
if textFormat == "plain":
|
if textFormat == "plain":
|
||||||
mb.setTextFormat(Qt.TextFormat.PlainText)
|
mb.setTextFormat(Qt.TextFormat.PlainText)
|
||||||
elif textFormat == "rich":
|
elif textFormat == "rich":
|
||||||
|
@ -967,8 +967,8 @@ def show_in_folder(path: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _show_in_folder_win32(path: str) -> None:
|
def _show_in_folder_win32(path: str) -> None:
|
||||||
import win32con # pylint: disable=import-error
|
import win32con
|
||||||
import win32gui # pylint: disable=import-error
|
import win32gui
|
||||||
|
|
||||||
from aqt import mw
|
from aqt import mw
|
||||||
|
|
||||||
|
@ -1263,12 +1263,12 @@ def opengl_vendor() -> str | None:
|
||||||
# Can't use versionFunctions there
|
# Can't use versionFunctions there
|
||||||
return None
|
return None
|
||||||
|
|
||||||
vp = QOpenGLVersionProfile() # type: ignore # pylint: disable=undefined-variable
|
vp = QOpenGLVersionProfile() # type: ignore
|
||||||
vp.setVersion(2, 0)
|
vp.setVersion(2, 0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
vf = ctx.versionFunctions(vp) # type: ignore
|
vf = ctx.versionFunctions(vp) # type: ignore
|
||||||
except ImportError as e:
|
except ImportError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if vf is None:
|
if vf is None:
|
||||||
|
|
|
@ -980,7 +980,6 @@ def _create_ankiwebview_subclass(
|
||||||
/,
|
/,
|
||||||
**fixed_kwargs: Unpack[_AnkiWebViewKwargs],
|
**fixed_kwargs: Unpack[_AnkiWebViewKwargs],
|
||||||
) -> Type[AnkiWebView]:
|
) -> Type[AnkiWebView]:
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: _AnkiWebViewKwargs) -> None:
|
def __init__(self, *args: Any, **kwargs: _AnkiWebViewKwargs) -> None:
|
||||||
# user‑supplied kwargs override fixed kwargs
|
# user‑supplied kwargs override fixed kwargs
|
||||||
merged = cast(_AnkiWebViewKwargs, {**fixed_kwargs, **kwargs})
|
merged = cast(_AnkiWebViewKwargs, {**fixed_kwargs, **kwargs})
|
||||||
|
|
|
@ -100,7 +100,7 @@ _SHGetFolderPath.restype = _err_unless_zero
|
||||||
|
|
||||||
def _get_path_buf(csidl):
|
def _get_path_buf(csidl):
|
||||||
path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH)
|
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
|
return path_buf.value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@ fn write_header(out: &mut impl Write) -> Result<()> {
|
||||||
out.write_all(
|
out.write_all(
|
||||||
br#"# Copyright: Ankitects Pty Ltd and contributors
|
br#"# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html
|
||||||
# pylint: skip-file
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -43,11 +43,11 @@ except Exception as e:
|
||||||
print_error(
|
print_error(
|
||||||
f"Could not establish connection to Chromium remote debugger. Is Anki Open? Exception:\n{e}"
|
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:
|
if chrome.tabs is None:
|
||||||
print_error("Was unable to get active web views.")
|
print_error("Was unable to get active web views.")
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for tab_index, tab_data in enumerate(chrome.tabs):
|
for tab_index, tab_data in enumerate(chrome.tabs):
|
||||||
print(f"Reloading page: {tab_data['title']}")
|
print(f"Reloading page: {tab_data['title']}")
|
||||||
|
|
153
uv.lock
153
uv.lock
|
@ -97,16 +97,13 @@ sphinx = [
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "black" },
|
|
||||||
{ name = "colorama" },
|
|
||||||
{ name = "hatchling" },
|
{ name = "hatchling" },
|
||||||
{ name = "isort" },
|
|
||||||
{ name = "mock" },
|
{ name = "mock" },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "mypy-protobuf" },
|
{ name = "mypy-protobuf" },
|
||||||
{ name = "pychromedevtools" },
|
{ name = "pychromedevtools" },
|
||||||
{ name = "pylint" },
|
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
|
{ name = "ruff" },
|
||||||
{ name = "types-decorator" },
|
{ name = "types-decorator" },
|
||||||
{ name = "types-flask" },
|
{ name = "types-flask" },
|
||||||
{ name = "types-flask-cors" },
|
{ name = "types-flask-cors" },
|
||||||
|
@ -129,16 +126,13 @@ provides-extras = ["sphinx"]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "black" },
|
|
||||||
{ name = "colorama" },
|
|
||||||
{ name = "hatchling" },
|
{ name = "hatchling" },
|
||||||
{ name = "isort" },
|
|
||||||
{ name = "mock" },
|
{ name = "mock" },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "mypy-protobuf" },
|
{ name = "mypy-protobuf" },
|
||||||
{ name = "pychromedevtools" },
|
{ name = "pychromedevtools" },
|
||||||
{ name = "pylint" },
|
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
|
{ name = "ruff" },
|
||||||
{ name = "types-decorator" },
|
{ name = "types-decorator" },
|
||||||
{ name = "types-flask" },
|
{ name = "types-flask" },
|
||||||
{ name = "types-flask-cors" },
|
{ 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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "blinker"
|
name = "blinker"
|
||||||
version = "1.9.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "distro"
|
name = "distro"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -589,7 +535,7 @@ name = "importlib-metadata"
|
||||||
version = "8.7.0"
|
version = "8.7.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
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" }
|
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 = [
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "itsdangerous"
|
name = "itsdangerous"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -664,14 +601,14 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown"
|
name = "markdown"
|
||||||
version = "3.8.1"
|
version = "3.8.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
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')" },
|
{ 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 = [
|
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]]
|
[[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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "mock"
|
name = "mock"
|
||||||
version = "5.2.0"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1011,31 +930,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/e0/04/572466be7e93af410
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.19.1"
|
version = "2.19.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
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 = [
|
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" },
|
{ 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]]
|
|
||||||
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" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "send2trash"
|
name = "send2trash"
|
||||||
version = "1.8.3"
|
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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "trove-classifiers"
|
name = "trove-classifiers"
|
||||||
version = "2025.5.9.12"
|
version = "2025.5.9.12"
|
||||||
|
|
Loading…
Reference in a new issue