Merge branch 'main' into svelte5

This commit is contained in:
Abdo 2024-08-14 14:44:16 +03:00 committed by GitHub
commit add1248cd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
156 changed files with 813 additions and 731 deletions

View file

@ -22,7 +22,7 @@ echo "--- Ensure libs importable"
SKIP_RUN=1 ./run
echo "--- Check Rust libs"
cargo install cargo-deny --version 0.14.12
cargo install cargo-deny --version 0.14.24
cargo deny check
echo "--- Cleanup"

View file

@ -5,8 +5,8 @@
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
ignore = [
# safemem only used by makeapp
"RUSTSEC-2023-0081",
# gix only used by burn-train
"RUSTSEC-2024-0350",
]
[licenses]
@ -23,6 +23,7 @@ allow = [
"CC0-1.0",
"Unlicense",
"Zlib",
"Unicode-3.0",
]
confidence-threshold = 0.8
# eg { allow = ["Zlib"], name = "adler32", version = "*" },

View file

@ -1,10 +1,5 @@
[settings]
ensure_newline_before_comments=true
force_grid_wrap=0
include_trailing_comma=True
py_version=39
known_first_party=anki,aqt,tests
line_length=88
multi_line_output=3
profile=black
skip=
use_parentheses=True
extend_skip=qt/bundle

2
.ruff.toml Normal file
View file

@ -0,0 +1,2 @@
target-version = "py39"
extend-exclude = ["qt/bundle"]

View file

@ -148,6 +148,7 @@ user1823 <92206575+user1823@users.noreply.github.com>
Gustaf Carefall <https://github.com/Gustaf-C>
virinci <github.com/virinci>
snowtimeglass <snowtimeglass@gmail.com>
brishtibheja <sorata225yume@gmail.com>
Ben Olson <github.com/grepgrok>
Akash Reddy <akashreddy2003@gmail.com>
Lucio Sauer <watermanpaint@posteo.net>
@ -177,7 +178,14 @@ RRomeroJr <117.rromero@gmail.com>
Xidorn Quan <me@upsuper.org>
Alexander Bocken <alexander@bocken.org>
James Elmore <email@jameselmore.org>
Ian Samir Yep Manzano <https://github.com/isym444>
David Culley <6276049+davidculley@users.noreply.github.com>
Rastislav Kish <rastislav.kish@protonmail.com>
Expertium <https://github.com/Expertium>
Christian Donat <https://github.com/cdonat2>
Asuka Minato <https://asukaminato.eu.org>
Dillon Baldwin <https://github.com/DillBal>
Voczi <https://github.com/voczi>
********************
The text of the 3 clause BSD license follows:

12
Cargo.lock generated
View file

@ -1901,9 +1901,9 @@ dependencies = [
[[package]]
name = "fsrs"
version = "0.6.4"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70cec685337af48789e58cea6ef59ee9f01289d1083428b03fe14e76b98c817c"
checksum = "285d9b275f7d5a276f17006e9d92ea67fa9991187ae88760fa96705fba1f97aa"
dependencies = [
"burn",
"itertools 0.12.1",
@ -3859,9 +3859,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.64"
version = "0.10.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
@ -3891,9 +3891,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
version = "0.9.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [
"cc",
"libc",

View file

@ -35,7 +35,7 @@ git = "https://github.com/ankitects/linkcheck.git"
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
[workspace.dependencies.fsrs]
version = "0.6.4"
version = "1.1.4"
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
# path = "../open-spaced-repetition/fsrs-rs"

View file

@ -368,8 +368,8 @@ pub trait FilesHandle {
/// different variables. This is a shortcut for calling .expand_inputs()
/// and then .add_inputs_vec()
/// - If the variable name is non-empty, a variable of the same name will be
/// created so the file list can be accessed in the command. By convention,
/// this is often `in`.
/// created so the file list can be accessed in the command. By
/// convention, this is often `in`.
fn add_inputs(&mut self, variable: &'static str, inputs: impl AsRef<BuildInput>);
fn add_inputs_vec(&mut self, variable: &'static str, inputs: Vec<String>);
fn add_order_only_inputs(&mut self, variable: &'static str, inputs: impl AsRef<BuildInput>);
@ -394,10 +394,10 @@ pub trait FilesHandle {
/// - Each output automatically has $builddir/ prefixed to it if it does not
/// already start with it.
/// - If the variable name is non-empty, a variable of the same name will be
/// created so the file list can be accessed in the command. By convention,
/// this is often `out`.
/// - If subgroup is true, the files are also placed in a subgroup. Eg
/// if a rule `foo` exists and subgroup `bar` is provided, the files are
/// created so the file list can be accessed in the command. By
/// convention, this is often `out`.
/// - If subgroup is true, the files are also placed in a subgroup. Eg if a
/// rule `foo` exists and subgroup `bar` is provided, the files are
/// accessible via `:foo:bar`. The variable name must not be empty, or
/// called `out`.
fn add_outputs_ext(

View file

@ -223,18 +223,6 @@ impl BuildAction for SvelteCheck {
build.add_inputs("yarn", inputs![":yarn:bin"]);
build.add_inputs("", &self.inputs);
build.add_inputs("", inputs!["yarn.lock"]);
build.add_variable(
"compiler_warnings",
[
"a11y-click-events-have-key-events",
"a11y-no-noninteractive-tabindex",
"a11y-no-static-element-interactions",
]
.iter()
.map(|warning| format!("{}$:ignore", warning))
.collect::<Vec<_>>()
.join(","),
);
let hash = simple_hash(&self.tsconfig);
build.add_output_stamp(format!("tests/svelte-check.{hash}"));
}

View file

@ -251,7 +251,7 @@ impl BuildAction for PythonTest {
build.add_variable("folder", self.folder);
build.add_variable(
"pythonpath",
&self.python_path.join(if cfg!(windows) { ";" } else { ":" }),
self.python_path.join(if cfg!(windows) { ";" } else { ":" }),
);
build.add_env_var("PYTHONPATH", "$pythonpath");
build.add_env_var("ANKI_TEST_MODE", "1");

View file

@ -1252,7 +1252,7 @@
},
{
"name": "fsrs",
"version": "0.6.4",
"version": "1.1.4",
"authors": "Open Spaced Repetition",
"repository": "https://github.com/open-spaced-repetition/fsrs-rs",
"license": "BSD-3-Clause",
@ -2521,7 +2521,7 @@
},
{
"name": "openssl",
"version": "0.10.64",
"version": "0.10.66",
"authors": "Steven Fackler <sfackler@gmail.com>",
"repository": "https://github.com/sfackler/rust-openssl",
"license": "Apache-2.0",
@ -2548,7 +2548,7 @@
},
{
"name": "openssl-sys",
"version": "0.9.102",
"version": "0.9.103",
"authors": "Alex Crichton <alex@alexcrichton.com>|Steven Fackler <sfackler@gmail.com>",
"repository": "https://github.com/sfackler/rust-openssl",
"license": "MIT",

View file

@ -3,32 +3,29 @@
For info on contributing things other than code, such as translations, decks
and add-ons, please see https://docs.ankiweb.net/contrib
With most users now on 2.1, the past 2 years have been focused on paying down some
of the technical debt that Anki's codebase has built up over the years, and making
changes that will make future maintenance and refactoring easier. A lot of Anki's
"business logic" has been migrated to Rust, which AnkiMobile and AnkiDroid
can also take advantage of - previously a lot of effort was wasted writing the same
code for each platform, and then debugging differences in the implementations.
Considerable effort has also been put into improving the Python side of things,
with type hints added to the majority of the codebase, automatic linting/formatting,
and refactoring of parts of the code.
The import/export code remains to be done, and this will likely
take a number of months to work through. Until that is complete, new features
will not be the top priority, unless they are easy wins as part of the refactoring
process.
If you are planning to contribute any non-trivial changes, please reach out
on the support site before you begin work. Some areas (primarily pylib/) are
likely to change/conflict with other work, and larger changes will likely need
to wait until the refactoring process is done.
## Help wanted
If you'd like to contribute but don't know what to work on, please take a look
at the [issues tab](https://github.com/ankitects/anki/issues) of the Anki repo
on GitHub.
## Larger changes
Before starting work on larger changes, especially ones that aren't listed on the
issue tracker, please reach out on the forums before you begin work, so we can let
you know whether they're likely to be accepted or not. When you spent a bunch of time
on a PR that ends up getting rejected, it's no fun for either you or us.
## Refactoring
Please avoid PRs that focus on refactoring. Every PR has a cost to review, and a chance
of introducing accidental regressions, and often these costs are not worth it for
slightly more elegant code.
That's not to say there's no value in refactoring. But such changes are usually better done
in a PR that happens to be working in the same area - for example, making small changes
to the code as part of fixing a bug, or a larger refactor when introducing a new feature.
## Type hints
Most of Anki's Python code now has type hints, which improve code completion,

View file

@ -1,4 +1,4 @@
FROM rust:1.76-alpine3.19 AS builder
FROM rust:1.79-alpine3.20 AS builder
ARG ANKI_VERSION
@ -8,7 +8,7 @@ RUN cargo install --git https://github.com/ankitects/anki.git \
--root /anki-server \
anki-sync-server
FROM alpine:3.19.1
FROM alpine:3.20
RUN adduser -D -h /home/anki anki
@ -25,8 +25,9 @@ EXPOSE ${SYNC_PORT}
CMD ["anki-sync-server"]
# TODO - consider exposing endpoint /health to check on health cause currently it will return 404 error
# HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
# CMD wget -qO- http://localhost:${SYNC_PORT} || exit 1
# This health check will work for Anki versions 24.06.3 and newer.
# For older versions, it may incorrectly report an unhealthy status, which should not be the case.
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:${SYNC_PORT}/health || exit 1
LABEL maintainer="Jean Khawand <jk@jeankhawand.com>"

@ -1 +1 @@
Subproject commit 173f043875bb38607d7d31bfcb0eb9874b8e4f3a
Subproject commit ababc05c46db93309ac50e7f80c10d6559314aa1

View file

@ -61,7 +61,7 @@ deck-config-today-only = Today only
deck-config-learning-steps = Learning steps
# Please don't translate `1m`, `2d`
-deck-config-delay-hint = Delays are typically minutes (eg `1m`) or days (eg `2d`), but hours (eg `1h`) and seconds (eg `30s`) are also supported.
-deck-config-delay-hint = Delays are typically minutes (e.g. `1m`) or days (e.g. `2d`), but hours (e.g. `1h`) and seconds (e.g. `30s`) are also supported.
deck-config-learning-steps-tooltip =
One or more delays, separated by spaces. The first delay will be used
when you press the `Again` button on a new card, and is 1 minute by default.
@ -100,7 +100,7 @@ deck-config-leech-threshold-tooltip =
think of a mnemonic to help you remember it.
# See actions-suspend-card and scheduling-tag-only for the wording
deck-config-leech-action-tooltip =
`Tag Only`: Add a "leech" tag to the note, and display a pop-up.
`Tag Only`: Add a 'leech' tag to the note, and display a pop-up.
`Suspend Card`: In addition to tagging the note, hide the card until it is
manually unsuspended.
@ -148,8 +148,8 @@ deck-config-new-gather-priority-tooltip-2 =
the latest-added first.
`Random notes`: gathers cards of randomly selected notes. When sibling burying is
disabled, this allows all cards of a note to be seen in a session (eg. both a front->back
and back->front card)
disabled, this allows all cards of a note to be seen in a session (e.g. both a front→back
and back→front card).
`Random cards`: gathers cards completely randomly.
deck-config-new-gather-priority-deck = Deck
@ -234,7 +234,7 @@ deck-config-stop-timer-on-answer-tooltip =
## Auto Advance section
deck-config-seconds-to-show-question = Seconds to show question for
deck-config-seconds-to-show-question-tooltip-2 = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
deck-config-seconds-to-show-question-tooltip-3 = When auto advance is activated, the number of seconds to wait before applying the question action. Set to 0 to disable.
deck-config-seconds-to-show-answer = Seconds to show answer for
deck-config-seconds-to-show-answer-tooltip-2 = When auto advance is activated, the number of seconds to wait before applying the answer action. Set to 0 to disable.
deck-config-question-action-show-answer = Show Answer
@ -242,8 +242,8 @@ deck-config-question-action-show-reminder = Show Reminder
deck-config-question-action = Question action
deck-config-question-action-tool-tip = The action to perform after the question is shown, and time has elapsed.
deck-config-answer-action = Answer action
deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one.
deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question.
deck-config-answer-action-tooltip-2 = The action to perform after the answer is shown, and time has elapsed.
deck-config-wait-for-audio-tooltip-2 = Wait for audio to finish before automatically applying the question action or answer action.
## Audio section
@ -251,7 +251,7 @@ deck-config-audio-title = Audio
deck-config-disable-autoplay = Don't play audio automatically
deck-config-disable-autoplay-tooltip =
When enabled, Anki will not play audio automatically.
It can be played manually by clicking/tapping on an audio icon, or by using the replay audio action.
It can be played manually by clicking/tapping on an audio icon, or by using the Replay action.
deck-config-skip-question-when-replaying = Skip question when replaying answer
deck-config-always-include-question-audio-tooltip =
Whether the question audio should be included when the Replay action is
@ -350,7 +350,7 @@ deck-config-compute-optimal-weights = Optimize FSRS parameters
deck-config-compute-minimum-recommended-retention = Minimum recommended retention
deck-config-optimize-button = Optimize
deck-config-compute-button = Compute
deck-config-ignore-before = Ignore reviews before
deck-config-ignore-before = Ignore cards reviewed before
deck-config-optimize-all-tip = You can optimize all presets at once by using the dropdown button next to "Save".
deck-config-evaluate-button = Evaluate
deck-config-desired-retention = Desired retention
@ -368,7 +368,7 @@ deck-config-reschedule-cards-on-change = Reschedule cards on change
deck-config-fsrs-tooltip =
Affects the entire collection.
The Free Spaced Repetition Scheduler (FSRS) is an alternative to Anki's legacy SuperMemo 2 (SM2) scheduler.
The Free Spaced Repetition Scheduler (FSRS) is an alternative to Anki's legacy SuperMemo 2 (SM-2) scheduler.
By more accurately determining when you are likely to forget, it can help you remember
more material in the same amount of time. This setting is shared by all deck presets.
@ -388,11 +388,11 @@ deck-config-historical-retention-tooltip =
the missing reviews.
Your review history may be incomplete for two reasons:
1. Because you've used the 'ignore reviews before' option.
1. Because you're using the 'ignore cards reviewed before' option.
2. Because you previously deleted review logs to free up space, or imported material from a different
SRS program.
The latter is quite rare, so unless you've used the former option, you probably don't need to adjust
The latter is quite rare, so unless you're using the former option, you probably don't need to adjust
this setting.
deck-config-weights-tooltip2 =
FSRS parameters affect how cards are scheduled. Anki will start with default parameters. You can use
@ -406,12 +406,12 @@ deck-config-reschedule-cards-on-change-tooltip =
will be changed.
deck-config-reschedule-cards-warning =
Depending on your desired retention, this can result in a large number of cards becoming
due, so is not recommended when first switching from SM2.
due, so is not recommended when first switching from SM-2.
Use this option sparingly, as it will add a review entry to each of your cards, and
increase the size of your collection.
deck-config-ignore-before-tooltip =
If set, reviews before the provided date will be ignored when optimizing & evaluating FSRS parameters.
deck-config-ignore-before-tooltip-2 =
If set, cards reviewed before the provided date will be ignored when optimizing FSRS parameters.
This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons.
deck-config-compute-optimal-weights-tooltip2 =
When you click the Optimize button, FSRS will analyze your review history, and generate parameters that are
@ -469,6 +469,12 @@ deck-config-bury-tooltip =
When using the V3 scheduler, interday learning cards can also be buried. Interday
learning cards are cards with a current learning step of one or more days.
deck-config-seconds-to-show-question-tooltip = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one.
deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question.
deck-config-ignore-before-tooltip =
If set, reviews before the provided date will be ignored when optimizing & evaluating FSRS parameters.
This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons.
deck-config-compute-optimal-retention-tooltip =
This tool assumes you're starting with 0 cards, and will attempt to calculate the amount of material you'll
be able to retain in the given time frame. The estimated retention will greatly depend on your inputs, and
@ -505,3 +511,4 @@ deck-config-compute-optimal-retention-tooltip3 =
set your desired retention to. You may wish to choose a higher desired retention, if youre willing to trade more study
time for a greater recall rate. Setting your desired retention lower than the minimum is not recommended, as it will
lead to a higher workload, because of the high forgetting rate.
deck-config-seconds-to-show-question-tooltip-2 = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.

View file

@ -41,6 +41,7 @@ preferences-theme-follow-system = Follow System
preferences-theme-light = Light
preferences-theme-dark = Dark
preferences-v3-scheduler = V3 scheduler
preferences-check-for-updates = Check for program updates
preferences-ignore-accents-in-search = Ignore accents in search (slower)
preferences-backup-explanation =
Anki periodically backs up your collection. After backups are more than 2 days old,

View file

@ -152,7 +152,7 @@ statistics-cards-due =
}
statistics-backlog-checkbox = Backlog
statistics-intervals-title = Review Intervals
statistics-intervals-subtitle = Delays until reviews are shown again.
statistics-intervals-subtitle = Delays until review cards are shown again.
statistics-intervals-day-range =
{ $cards ->
[one] { $cards } card with a { $daysStart }~{ $daysEnd } day interval

@ -1 +1 @@
Subproject commit 1e8db8f33c0da127e67312aeb77f1c3aa901af77
Subproject commit 8cc8e3447cca79a7a8720e118388f6383c830eee

View file

@ -9,8 +9,8 @@
"dev": "cd ts && vite dev",
"build": "cd ts && vite build",
"preview": "cd ts && vite preview",
"svelte-check:once": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --fail-on-warnings --threshold warning --compiler-warnings a11y-click-events-have-key-events:ignore,a11y-no-noninteractive-tabindex:ignore,a11y-no-static-element-interactions:ignore",
"svelte-check": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch --compiler-warnings a11y-click-events-have-key-events:ignore,a11y-no-noninteractive-tabindex:ignore,a11y-no-static-element-interactions:ignore",
"svelte-check:once": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --fail-on-warnings --threshold warning",
"svelte-check": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"vitest:once": "cd ts && vitest run",
"vitest": "cd ts && vitest"
},

View file

@ -402,27 +402,26 @@ message ComputeOptimalRetentionResponse {
float optimal_retention = 1;
}
message OptimalRetentionParameters {
double recall_secs_hard = 1;
double recall_secs_good = 2;
double recall_secs_easy = 3;
double forget_secs = 4;
double learn_secs = 5;
double first_rating_probability_again = 6;
double first_rating_probability_hard = 7;
double first_rating_probability_good = 8;
double first_rating_probability_easy = 9;
double review_rating_probability_hard = 10;
double review_rating_probability_good = 11;
double review_rating_probability_easy = 12;
}
message GetOptimalRetentionParametersRequest {
string search = 1;
}
message GetOptimalRetentionParametersResponse {
OptimalRetentionParameters params = 1;
uint32 deck_size = 1;
uint32 learn_span = 2;
float max_cost_perday = 3;
float max_ivl = 4;
repeated float learn_costs = 5;
repeated float review_costs = 6;
repeated float first_rating_prob = 7;
repeated float review_rating_prob = 8;
repeated float first_rating_offsets = 9;
repeated float first_session_lens = 10;
float forget_rating_offset = 11;
float forget_session_len = 12;
float loss_aversion = 13;
uint32 learn_limit = 14;
uint32 review_limit = 15;
}
message EvaluateWeightsRequest {

View file

@ -6,8 +6,9 @@ from __future__ import annotations
import sys
import time
import traceback
from collections.abc import Iterable, Sequence
from threading import current_thread, main_thread
from typing import TYPE_CHECKING, Any, Iterable, Sequence
from typing import TYPE_CHECKING, Any
from weakref import ref
from markdown import markdown

View file

@ -8,7 +8,8 @@ import os
import pathlib
import sys
import traceback
from typing import TYPE_CHECKING, Any, Callable, Union
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Union
from anki._vendor import stringcase # type: ignore

View file

@ -1,6 +1,5 @@
from typing import Union
class Backend:
@classmethod
def command(cls, service: int, method: int, data: bytes) -> bytes: ...

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import pprint
import time
from typing import NewType
import anki # pylint: disable=unused-import
import anki.collection

View file

@ -3,7 +3,8 @@
from __future__ import annotations
from typing import Any, Generator, Iterable, Literal, Sequence, Union, cast
from collections.abc import Generator, Iterable, Sequence
from typing import Any, Literal, Union, cast
from anki import (
ankiweb_pb2,

View file

@ -4,8 +4,9 @@
from __future__ import annotations
import re
from collections.abc import Callable, Iterable, Sequence
from re import Match
from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, Union
from typing import TYPE_CHECKING, Any, Union
if TYPE_CHECKING:
import anki._backend

View file

@ -4,7 +4,8 @@
from __future__ import annotations
import copy
from typing import TYPE_CHECKING, Any, Iterable, NewType, Sequence
from collections.abc import Iterable, Sequence
from typing import TYPE_CHECKING, Any, NewType
if TYPE_CHECKING:
import anki

View file

@ -13,8 +13,9 @@ import threading
import time
import unicodedata
import zipfile
from collections.abc import Sequence
from io import BufferedWriter
from typing import Any, Optional, Sequence
from typing import Any
from zipfile import ZipFile
from anki import hooks
@ -26,16 +27,16 @@ from anki.utils import ids2str, namedtmp, split_fields, strip_html
class Exporter:
includeHTML: bool | None = None
ext: Optional[str] = None
includeTags: Optional[bool] = None
includeSched: Optional[bool] = None
includeMedia: Optional[bool] = None
ext: str | None = None
includeTags: bool | None = None
includeSched: bool | None = None
includeMedia: bool | None = None
def __init__(
self,
col: Collection,
did: Optional[DeckId] = None,
cids: Optional[list[CardId]] = None,
did: DeckId | None = None,
cids: list[CardId] | None = None,
) -> None:
self.col = col.weakref()
self.did = did

View file

@ -94,8 +94,8 @@ class ForeignCard:
class ForeignNote:
fields: list[str] = field(default_factory=list)
tags: list[str] = field(default_factory=list)
notetype: Union[str, NotetypeId] = ""
deck: Union[str, DeckId] = ""
notetype: str | NotetypeId = ""
deck: str | DeckId = ""
cards: list[ForeignCard] = field(default_factory=list)
@ -103,7 +103,7 @@ class ForeignNote:
class ForeignData:
notes: list[ForeignNote] = field(default_factory=list)
notetypes: list[ForeignNotetype] = field(default_factory=list)
default_deck: Union[str, DeckId] = ""
default_deck: str | DeckId = ""
def serialize(self) -> str:
return json.dumps(self, cls=ForeignDataEncoder, separators=(",", ":"))

View file

@ -17,7 +17,6 @@ Notetype | Card Type
import re
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Tuple, Type
from anki.db import DB
from anki.decks import DeckId
@ -38,7 +37,7 @@ def serialize(db_path: str, deck_id: DeckId) -> str:
def gather_data(db: DB, deck_id: DeckId) -> ForeignData:
facts = gather_facts(db)
gather_cards_into_facts(db, facts)
used_fact_views: dict[Type[MnemoFactView], bool] = {}
used_fact_views: dict[type[MnemoFactView], bool] = {}
notes = [fact.foreign_note(used_fact_views) for fact in facts.values()]
notetypes = [fact_view.foreign_notetype() for fact_view in used_fact_views]
return ForeignData(notes, notetypes, deck_id)
@ -54,7 +53,7 @@ def open_mnemosyne_db(db_path: str) -> DB:
class MnemoFactView(ABC):
notetype: str
field_keys: Tuple[str, ...]
field_keys: tuple[str, ...]
@classmethod
@abstractmethod
@ -162,7 +161,7 @@ class MnemoFact:
cards: list[MnemoCard] = field(default_factory=list)
def foreign_note(
self, used_fact_views: dict[Type[MnemoFactView], bool]
self, used_fact_views: dict[type[MnemoFactView], bool]
) -> ForeignNote:
fact_view = self.fact_view()
used_fact_views[fact_view] = True
@ -173,7 +172,7 @@ class MnemoFact:
cards=self.foreign_cards(),
)
def fact_view(self) -> Type[MnemoFactView]:
def fact_view(self) -> type[MnemoFactView]:
try:
fact_view = self.cards[0].fact_view_id
except IndexError as err:
@ -190,7 +189,7 @@ class MnemoFact:
raise Exception(f"Fact {id} has unknown fact view: {fact_view}")
def anki_fields(self, fact_view: Type[MnemoFactView]) -> list[str]:
def anki_fields(self, fact_view: type[MnemoFactView]) -> list[str]:
return [munge_field(self.fields.get(k, "")) for k in fact_view.field_keys]
def anki_tags(self) -> list[str]:

View file

@ -14,6 +14,9 @@ modifying it.
from __future__ import annotations
from collections.abc import Callable
from typing import Any
import decorator
# You can find the definitions in ../tools/genhooks.py
@ -32,7 +35,7 @@ def runHook(hook: str, *args: Any) -> None:
for func in hookFuncs:
try:
func(*args)
except:
except Exception:
hookFuncs.remove(func)
raise
@ -43,7 +46,7 @@ def runFilter(hook: str, arg: Any, *args: Any) -> Any:
for func in hookFuncs:
try:
arg = func(arg, *args)
except:
except Exception:
hookFuncs.remove(func)
raise
return arg

View file

@ -9,7 +9,8 @@ from __future__ import annotations
import io
import os
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
import requests
from requests import Response

View file

@ -1,7 +1,8 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from typing import Any, Callable, Sequence, Type, Union
from collections.abc import Callable, Sequence
from typing import Any, Type, Union
import anki
from anki.collection import Collection

View file

@ -2,10 +2,11 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# pylint: disable=invalid-name
from __future__ import annotations
import os
import unicodedata
from typing import Optional
from typing import Any
from anki.cards import CardId
from anki.collection import Collection
@ -31,7 +32,7 @@ class MediaMapInvalid(Exception):
class Anki2Importer(Importer):
needMapper = False
deckPrefix: Optional[str] = None
deckPrefix: str | None = None
allowUpdate = True
src: Collection
dst: Collection
@ -409,7 +410,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""",
if fname.startswith("_") and not self.dst.media.have(fname):
self._writeDstMedia(fname, self._srcMediaData(fname))
def _mediaData(self, fname: str, dir: Optional[str] = None) -> bytes:
def _mediaData(self, fname: str, dir: str | None = None) -> bytes:
if not dir:
dir = self.src.media.dir()
path = os.path.join(dir, fname)

View file

@ -2,12 +2,13 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# pylint: disable=invalid-name
from __future__ import annotations
import json
import os
import unicodedata
import zipfile
from typing import Any, Optional
from typing import Any
from anki.importing.anki2 import Anki2Importer, MediaMapInvalid
from anki.utils import tmpfile
@ -15,7 +16,7 @@ from anki.utils import tmpfile
class AnkiPackageImporter(Anki2Importer):
nameToNum: dict[str, str]
zip: Optional[zipfile.ZipFile]
zip: zipfile.ZipFile | None
def run(self) -> None: # type: ignore
# extract the deck from the zip file

View file

@ -2,8 +2,9 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# pylint: disable=invalid-name
from __future__ import annotations
from typing import Any, Optional
from typing import Any
from anki.collection import Collection
from anki.utils import max_id
@ -15,7 +16,7 @@ from anki.utils import max_id
class Importer:
needMapper = False
needDelimiter = False
dst: Optional[Collection]
dst: Collection | None
def __init__(self, col: Collection, file: str) -> None:
self.file = file

View file

@ -7,7 +7,7 @@ from __future__ import annotations
import csv
import re
from typing import Any, Optional, TextIO
from typing import Any, TextIO
from anki.collection import Collection
from anki.importing.noteimp import ForeignNote, NoteImporter
@ -20,12 +20,12 @@ class TextImporter(NoteImporter):
def __init__(self, col: Collection, file: str) -> None:
NoteImporter.__init__(self, col, file)
self.lines = None
self.fileobj: Optional[TextIO] = None
self.delimiter: Optional[str] = None
self.fileobj: TextIO | None = None
self.delimiter: str | None = None
self.tagsToAdd: list[str] = []
self.numFields = 0
self.dialect: Optional[Any]
self.data: Optional[str | list[str]]
self.dialect: Any | None
self.data: str | list[str] | None
def foreignNotes(self) -> list[ForeignNote]:
self.open()
@ -99,15 +99,15 @@ class TextImporter(NoteImporter):
if not self.delimiter:
try:
self.dialect = sniffer.sniff("\n".join(self.data[:10]), self.patterns)
except:
except Exception:
try:
self.dialect = sniffer.sniff(self.data[0], self.patterns)
except:
except Exception:
pass
if self.dialect:
try:
reader = csv.reader(self.data, self.dialect, doublequote=True)
except:
except Exception:
err()
else:
if not self.delimiter:
@ -126,7 +126,7 @@ class TextImporter(NoteImporter):
if row:
self.numFields = len(row)
break
except:
except Exception:
err()
self.initMapping()

View file

@ -7,7 +7,7 @@ from __future__ import annotations
import html
import unicodedata
from typing import Optional, Union
from typing import Union
from anki.collection import Collection
from anki.config import Config
@ -76,8 +76,8 @@ class NoteImporter(Importer):
needDelimiter = False
allowHTML = False
importMode = UPDATE_MODE
mapping: Optional[list[str]]
tagModified: Optional[str]
mapping: list[str] | None
tagModified: str | None
def __init__(self, col: Collection, file: str) -> None:
Importer.__init__(self, col, file)
@ -268,7 +268,7 @@ class NoteImporter(Importer):
def updateData(
self, n: ForeignNote, id: NoteId, sflds: list[str]
) -> Optional[Updates]:
) -> Updates | None:
self._ids.append(id)
self.processFields(n, sflds)
if self._tagsMapped:
@ -316,9 +316,7 @@ where id = ? and flds != ?""",
changes2 = self.col.db.scalar("select total_changes()")
self.updateCount = changes2 - changes
def processFields(
self, note: ForeignNote, fields: Optional[list[str]] = None
) -> None:
def processFields(self, note: ForeignNote, fields: list[str] | None = None) -> None:
if not fields:
fields = [""] * len(self.model["flds"])
for c, f in enumerate(self.mapping):

View file

@ -3,13 +3,13 @@
# pytype: disable=attribute-error
# type: ignore
# pylint: disable=C
from __future__ import annotations
import re
import sys
import time
import unicodedata
from string import capwords
from typing import Optional, Union
from xml.dom import minidom
from xml.dom.minidom import Element, Text
@ -329,7 +329,7 @@ class SupermemoXmlImporter(NoteImporter):
self.logger("Load done.")
# PARSE
def parse(self, node: Optional[Union[Text, Element]] = None) -> None:
def parse(self, node: Text | Element | None = None) -> None:
"Parse method - parses document elements"
if node is None and self.xmldoc is not None:

View file

@ -180,9 +180,16 @@ def set_lang(lang: str) -> None:
tr_legacyglobal.backend = weakref.ref(current_i18n)
def get_def_lang(lang: str | None = None) -> tuple[int, str]:
"""Return lang converted to name used on disk and its index, defaulting to system language
def get_def_lang(user_lang: str | None = None) -> tuple[int, str]:
"""Return user_lang converted to name used on disk and its index, defaulting to system language
or English if not available."""
def get_index_of_language(wanted_locale: str) -> int | None:
for i, (_, locale_) in enumerate(langs):
if locale_ == wanted_locale:
return i
return None
try:
# 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():
@ -192,28 +199,30 @@ def get_def_lang(lang: str | None = None) -> tuple[int, str]:
# this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US
# further below
(sys_lang, enc) = locale.getlocale()
except:
except Exception:
# fails on osx
sys_lang = "en_US"
user_lang = lang
if user_lang in compatMap:
user_lang = compatMap[user_lang]
idx = None
lang = None
en_idx = None
for preferred_lang in (user_lang, sys_lang):
for lang_idx, (name, code) in enumerate(langs):
if code == "en_US":
en_idx = lang_idx
if code == preferred_lang:
idx = lang_idx
idx = get_index_of_language(preferred_lang)
is_language_supported = idx is not None
if is_language_supported:
assert preferred_lang is not None
lang = preferred_lang
if idx is not None:
break
# if the specified language and the system language aren't available, revert to english
if idx is None:
idx = en_idx
is_preferred_language_supported = idx is not None
if not is_preferred_language_supported:
lang = "en_US"
idx = get_index_of_language(lang)
is_english_supported = idx is not None
if not is_english_supported:
raise AssertionError("English is supposed to be a supported language.")
assert idx is not None and lang is not None
return (idx, lang)

View file

@ -170,7 +170,7 @@ def _err_msg(col: anki.collection.Collection, type: str, texpath: str) -> str:
if not log:
raise Exception()
msg += f"<small><pre>{html.escape(log)}</pre></small>"
except:
except Exception:
msg += col.tr.media_have_you_installed_latex_and_dvipngdvisvgm()
return msg

View file

@ -8,7 +8,7 @@ import pprint
import re
import sys
import time
from typing import Callable, Sequence
from collections.abc import Callable, Sequence
from anki import media_pb2
from anki._legacy import DeprecatedNamesMixin, deprecated_keywords

View file

@ -7,7 +7,8 @@ import copy
import pprint
import sys
import time
from typing import Any, NewType, Sequence, Union
from collections.abc import Sequence
from typing import Any, NewType, Union
import anki # pylint: disable=unused-import
import anki.collection
@ -312,7 +313,7 @@ class ModelManager(DeprecatedNamesMixin):
def rename_field(
self, notetype: NotetypeDict, field: FieldDict, new_name: str
) -> None:
if not field in notetype["flds"]:
if field not in notetype["flds"]:
raise Exception("invalid field")
field["name"] = new_name
@ -388,8 +389,8 @@ and notes.mid = ? and cards.ord = ?""",
To get defaults, use
input = ChangeNotetypeRequest()
input.ParseFromString(col.models.change_notetype_info(...))
info = col.models.change_notetype_info(...)
input = info.input
input.note_ids.extend([...])
The new_fields and new_templates lists are relative to the new notetype's

View file

@ -4,7 +4,8 @@
from __future__ import annotations
import copy
from typing import NewType, Sequence
from collections.abc import Sequence
from typing import NewType
import anki # pylint: disable=unused-import
import anki.cards
@ -94,21 +95,28 @@ class Note(DeprecatedNamesMixin):
self,
ord: int = 0,
*,
custom_note_type: NotetypeDict = None,
custom_template: TemplateDict = None,
custom_note_type: NotetypeDict | None = None,
custom_template: TemplateDict | None = None,
fill_empty: bool = False,
) -> anki.cards.Card:
card = anki.cards.Card(self.col)
card.ord = ord
card.did = anki.decks.DEFAULT_DECK_ID
model = custom_note_type or self.note_type()
template = copy.copy(
custom_template
or (
model["tmpls"][ord] if model["type"] == MODEL_STD else model["tmpls"][0]
)
)
if custom_note_type is None:
model = self.note_type()
else:
model = custom_note_type
if model is None:
raise NotImplementedError
if custom_template is not None:
template = custom_template
elif model["type"] == MODEL_STD:
template = model["tmpls"][ord]
else:
template = model["tmpls"][0]
template = copy.copy(template)
# may differ in cloze case
template["ord"] = card.ord
@ -171,10 +179,7 @@ class Note(DeprecatedNamesMixin):
return self.col.tags.in_list(tag, self.tags)
def remove_tag(self, tag: str) -> None:
rem = []
for tag_ in self.tags:
if tag_.lower() == tag.lower():
rem.append(tag_)
rem = [tag_ for tag_ in self.tags if tag_.lower() == tag.lower()]
for tag_ in rem:
self.tags.remove(tag_)

View file

@ -22,7 +22,8 @@ ScheduleCardsAsNewDefaults = scheduler_pb2.ScheduleCardsAsNewDefaultsResponse
FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
RepositionDefaults = scheduler_pb2.RepositionDefaultsResponse
from typing import Sequence, overload
from collections.abc import Sequence
from typing import overload
from anki import config_pb2
from anki.cards import CardId

View file

@ -5,8 +5,6 @@
from __future__ import annotations
from typing import Optional
from anki._legacy import deprecated
from anki.cards import Card, CardId
from anki.consts import (
@ -54,7 +52,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
print("_nextDueMsg() is obsolete")
return ""
def rebuildDyn(self, did: Optional[DeckId] = None) -> Optional[int]:
def rebuildDyn(self, did: DeckId | None = None) -> int | None:
did = did or self.col.decks.selected()
count = self.rebuild_filtered_deck(did).count or None
if not count:
@ -63,7 +61,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
self.col.decks.select(did)
return count
def emptyDyn(self, did: Optional[DeckId], lim: Optional[str] = None) -> None:
def emptyDyn(self, did: DeckId | None, lim: str | None = None) -> None:
if lim is None:
self.empty_filtered_deck(did)
return

View file

@ -14,7 +14,8 @@ as '2' internally.
from __future__ import annotations
from typing import Literal, Optional, Sequence
from collections.abc import Sequence
from typing import Any, Literal
from anki import frontend_pb2, scheduler_pb2
from anki._legacy import deprecated
@ -109,7 +110,7 @@ class Scheduler(SchedulerBaseWithLegacy):
# backend automatically resets queues as operations are performed
pass
def getCard(self) -> Optional[Card]:
def getCard(self) -> Card | None:
"""Fetch the next card from the queue. None if finished."""
try:
queued_card = self.get_queued_cards().cards[0]
@ -125,7 +126,7 @@ class Scheduler(SchedulerBaseWithLegacy):
"Don't use this, it is a stop-gap until this code is refactored."
return not self.get_queued_cards().cards
def counts(self, card: Optional[Card] = None) -> tuple[int, int, int]:
def counts(self, card: Card | None = None) -> tuple[int, int, int]:
info = self.get_queued_cards()
return (info.new_count, info.learning_count, info.review_count)

View file

@ -8,7 +8,8 @@ from __future__ import annotations
import json
import random
import time
from typing import Sequence
from collections.abc import Sequence
from typing import Any
import anki.cards
import anki.collection
@ -721,7 +722,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE
tot = bad + good
try:
pct = good / float(tot) * 100
except:
except Exception:
pct = 0
i.append(
"Correct: <b>%(pct)0.2f%%</b><br>(%(good)d of %(tot)d)"
@ -973,7 +974,7 @@ from cards where did in %s"""
else:
conf["legend"] = {"container": "#%sLegend" % id, "noColumns": 10}
conf["series"] = dict(stack=True)
if not "yaxis" in conf:
if "yaxis" not in conf:
conf["yaxis"] = {}
conf["yaxis"]["labelWidth"] = 40
if "xaxis" not in conf:

View file

@ -3,7 +3,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable
from collections.abc import Callable
from typing import TYPE_CHECKING, Any
import anki.collection
import anki.models

View file

@ -13,7 +13,8 @@ from __future__ import annotations
import pprint
import re
from typing import Collection, Match, Sequence
from collections.abc import Collection, Sequence
from typing import Match
import anki # pylint: disable=unused-import
import anki.collection

View file

@ -28,8 +28,9 @@ template_legacy.py file, using the legacy addHook() system.
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any, Sequence, Union
from typing import Any, Union
import anki
import anki.cards

View file

@ -13,9 +13,10 @@ import subprocess
import sys
import tempfile
import time
from collections.abc import Callable, Iterable, Iterator
from contextlib import contextmanager
from hashlib import sha1
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator
from typing import TYPE_CHECKING, Any
from anki._legacy import DeprecatedNamesMixinForModule
from anki.dbproxy import DBProxy
@ -28,7 +29,7 @@ try:
to_json_bytes: Callable[[Any], bytes] = orjson.dumps
from_json_bytes = orjson.loads
except:
except Exception:
print("orjson is missing; DB operations will be slower")
def to_json_bytes(obj: Any) -> bytes:
@ -214,7 +215,7 @@ def call(argv: list[str], wait: bool = True, **kwargs: Any) -> int:
info = subprocess.STARTUPINFO() # type: ignore
try:
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
except:
except Exception:
# pylint: disable=no-member
info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore
else:
@ -285,7 +286,7 @@ def plat_desc() -> str:
else:
theos = system
break
except:
except Exception:
continue
return theos

View file

@ -26,7 +26,7 @@ def clear_tempfile(tf):
try:
tf.close()
os.unlink(tf.name)
except:
except Exception:
pass

View file

@ -4,7 +4,8 @@
import copy
import os
import time
from typing import Callable, Dict
from collections.abc import Callable
from typing import Dict
import pytest
@ -385,7 +386,7 @@ def test_reviews():
assert "leech" in c.note().tags
def review_limits_setup() -> tuple[anki.collection.Collection, Dict]:
def review_limits_setup() -> tuple[anki.collection.Collection, dict]:
col = getEmptyCol()
parent = col.decks.get(col.decks.id("parent"))

View file

@ -137,7 +137,9 @@ prefix = """\
from __future__ import annotations
from typing import Any, Callable, Sequence
from collections.abc import Callable, Sequence
from typing import Any
import anki
import anki.hooks
from anki.cards import Card

View file

@ -5,12 +5,13 @@
Code for generating hooks.
"""
from __future__ import annotations
import os
import subprocess
import sys
from dataclasses import dataclass
from operator import attrgetter
from typing import Optional
sys.path.append("pylib/anki/_vendor")
@ -23,19 +24,19 @@ class Hook:
name: str
# string of the typed arguments passed to the callback, eg
# ["kind: str", "val: int"]
args: list[str] = None
args: list[str] | None = None
# string of the return type. if set, hook is a filter.
return_type: Optional[str] = None
return_type: str | None = None
# if add-ons may be relying on the legacy hook name, add it here
legacy_hook: Optional[str] = None
legacy_hook: str | None = None
# if legacy hook takes no arguments but the new hook does, set this
legacy_no_args: bool = False
# if the hook replaces a deprecated one, add its name here
replaces: Optional[str] = None
replaces: str | None = None
# arguments that the hook being replaced took
replaced_hook_args: Optional[list[str]] = None
replaced_hook_args: list[str] | None = None
# docstring to add to hook class
doc: Optional[str] = None
doc: str | None = None
def callable(self) -> str:
"Convert args into a Callable."
@ -47,7 +48,7 @@ class Hook:
types_str = ", ".join(types)
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
def arg_names(self, args: Optional[list[str]]) -> list[str]:
def arg_names(self, args: list[str] | None) -> list[str]:
names = []
for arg in args or []:
if not arg:
@ -126,7 +127,7 @@ class {self.classname()}:
for hook in self._hooks:
try:
hook({", ".join(arg_names)})
except:
except Exception:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
@ -162,7 +163,7 @@ class {self.classname()}:
for filter in self._hooks:
try:
{arg_names[0]} = filter({", ".join(arg_names)})
except:
except Exception:
# if the hook fails, remove it
self._hooks.remove(filter)
raise

3
pyproject.toml Normal file
View file

@ -0,0 +1,3 @@
[tool.black]
target-version = ["py39", "py310", "py311", "py312"]
extend-exclude = "qt/bundle"

View file

@ -1,9 +1,5 @@
[settings]
ensure_newline_before_comments=true
force_grid_wrap=0
include_trailing_comma=True
py_version=39
profile=black
known_first_party=anki,aqt
line_length=88
multi_line_output=3
skip=aqt/forms,hooks_gen.py
use_parentheses=True
extend_skip=aqt/forms,hooks_gen.py

View file

@ -5,6 +5,8 @@ from __future__ import annotations
import logging
import sys
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, Union, cast
try:
import pip_system_certs.wrapt_requests
@ -50,7 +52,6 @@ import os
import tempfile
import traceback
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Optional, cast
import anki.lang
from anki._backend import RustBackend
@ -61,6 +62,7 @@ from anki.utils import checksum, is_lin, is_mac
from aqt import gui_hooks
from aqt.log import setup_logging
from aqt.qt import *
from aqt.qt import sip
from aqt.utils import TR, tr
if TYPE_CHECKING:
@ -92,8 +94,8 @@ appHelpSite = HELP_SITE
from aqt.main import AnkiQt # isort:skip
from aqt.profiles import ProfileManager, VideoDriver # isort:skip
profiler: Optional[cProfile.Profile] = None
mw: Optional[AnkiQt] = None # set on init
profiler: cProfile.Profile | None = None
mw: AnkiQt | None = None # set on init
import aqt.forms
@ -154,7 +156,7 @@ class DialogManager:
def allClosed(self) -> bool:
return not any(x[1] for x in self._dialogs.values())
def closeAll(self, onsuccess: Callable[[], None]) -> Optional[bool]:
def closeAll(self, onsuccess: Callable[[], None]) -> bool | None:
# can we close immediately?
if self.allClosed():
onsuccess()
@ -181,7 +183,7 @@ class DialogManager:
return True
def register_dialog(
self, name: str, creator: Union[Callable, type], instance: Optional[Any] = None
self, name: str, creator: Callable | type, instance: Any | None = None
) -> None:
"""Allows add-ons to register a custom dialog to be managed by Anki's dialog
manager, which ensures that only one copy of the window is open at once,
@ -219,19 +221,19 @@ dialogs = DialogManager()
# A reference to the Qt translator needs to be held to prevent it from
# being immediately deallocated.
_qtrans: Optional[QTranslator] = None
_qtrans: QTranslator | None = None
def setupLangAndBackend(
pm: ProfileManager,
app: QApplication,
force: Optional[str] = None,
force: str | None = None,
firstTime: bool = False,
) -> RustBackend:
global _qtrans
try:
locale.setlocale(locale.LC_ALL, "")
except:
except Exception:
pass
# add _ and ngettext globals used by legacy code
@ -288,7 +290,7 @@ def setupLangAndBackend(
class NativeEventFilter(QAbstractNativeEventFilter):
def nativeEventFilter(
self, eventType: Any, message: Any
) -> tuple[bool, Optional[sip.voidptr]]:
) -> tuple[bool, sip.voidptr | None]:
if eventType == "windows_generic_MSG":
import ctypes
@ -563,7 +565,7 @@ def run() -> None:
)
def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiApp]:
def _run(argv: list[str] | None = None, exec: bool = True) -> AnkiApp | None:
"""Start AnkiQt application or reuse an existing instance if one exists.
If the function is invoked with exec=False, the AnkiQt will not enter
@ -629,7 +631,7 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
pmLoadResult = pm.setupMeta()
Collection.initialize_backend_logging()
except:
except Exception:
# will handle below
traceback.print_exc()
pm = None
@ -671,11 +673,6 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
# we've signaled the primary instance, so we should close
return None
setup_logging(
pm.addon_logs(),
level=logging.DEBUG if int(os.getenv("ANKIDEV", "0")) else logging.INFO,
)
if not pm:
if i18n_setup:
QMessageBox.critical(
@ -687,6 +684,11 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
QMessageBox.critical(None, "Startup Failed", "Unable to create data folder")
return None
setup_logging(
pm.addon_logs(),
level=logging.DEBUG if int(os.getenv("ANKIDEV", "0")) else logging.INFO,
)
# disable icons on mac; this must be done before window created
if is_mac:
app.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus)
@ -719,7 +721,7 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
# we must have a usable temp dir
try:
tempfile.gettempdir()
except:
except Exception:
QMessageBox.critical(
None,
tr.qt_misc_error(),

View file

@ -5,8 +5,8 @@ from __future__ import annotations
import os
import sys
from collections.abc import Callable
from ctypes import CDLL, CFUNCTYPE, c_bool, c_char_p
from typing import Callable
import aqt
import aqt.utils

View file

@ -2,6 +2,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import platform
from collections.abc import Callable
import aqt.forms
from anki.lang import without_unicode_isolation
@ -86,6 +87,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"Christian Krause",
"Christian Rusche",
"Dave Druelinger",
"David Culley",
"David Smith",
"Dmitry Mikheev",
"Dotan Cohen",
@ -194,6 +196,8 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"Marko Sisovic",
"Lucas Scharenbroch",
"Antoine Q.",
"Ian Samir Yep Manzano",
"Asuka Minato",
)
)

View file

@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Callable, Optional
from collections.abc import Callable
import aqt.editor
import aqt.forms
@ -54,7 +54,7 @@ class AddCards(QMainWindow):
self.setupButtons()
self.col.add_image_occlusion_notetype()
self.history: list[NoteId] = []
self._last_added_note: Optional[Note] = None
self._last_added_note: Note | None = None
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
restoreGeom(self, "add")
gui_hooks.add_cards_did_init(self)
@ -178,7 +178,7 @@ class AddCards(QMainWindow):
break
# copy non-empty old fields
if (
not old_field_value in copied_field_names
old_field_value not in copied_field_names
and old_note.fields[old_idx]
):
new_note.fields[new_idx] = old_note.fields[old_idx]
@ -195,7 +195,7 @@ class AddCards(QMainWindow):
self, old_note.note_type(), new_note.note_type()
)
def _load_new_note(self, sticky_fields_from: Optional[Note] = None) -> None:
def _load_new_note(self, sticky_fields_from: Note | None = None) -> None:
note = self._new_note()
if old_note := sticky_fields_from:
flds = note.note_type()["flds"]
@ -209,7 +209,7 @@ class AddCards(QMainWindow):
self.setAndFocusNote(note)
def on_operation_did_execute(
self, changes: OpChanges, handler: Optional[object]
self, changes: OpChanges, handler: object | None
) -> None:
if (changes.notetype or changes.deck) and handler is not self.editor:
self.on_notetype_change(

View file

@ -9,13 +9,16 @@ import json
import logging
import os
import re
import sys
import traceback
import zipfile
from collections import defaultdict
from collections.abc import Callable, Iterable, Sequence
from concurrent.futures import Future
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import IO, Any, Callable, Iterable, Sequence, Union
from typing import IO, Any, Union
from urllib.parse import parse_qs, urlparse
from zipfile import ZipFile
@ -247,7 +250,7 @@ class AddonManager:
__import__(addon.dir_name)
except AbortAddonImport:
pass
except:
except Exception:
name = html.escape(addon.human_name())
page = addon.page()
if page:
@ -340,7 +343,7 @@ class AddonManager:
except json.JSONDecodeError as e:
print(f"json error in add-on {module}:\n{e}")
return dict()
except:
except Exception:
# missing meta file, etc
return dict()
@ -643,7 +646,7 @@ class AddonManager:
try:
with open(path, encoding="utf8") as f:
return json.load(f)
except:
except Exception:
return None
def set_config_help_action(self, module: str, action: Callable[[], str]) -> None:
@ -1083,9 +1086,11 @@ def download_addon(client: HttpClient, id: int) -> DownloadOk | DownloadError:
data = client.stream_content(resp)
fname = re.match(
match = re.match(
"attachment; filename=(.+)", resp.headers["content-disposition"]
).group(1)
)
assert match is not None
fname = match.group(1)
meta = extract_meta_from_download_url(resp.url)
@ -1113,11 +1118,14 @@ def extract_meta_from_download_url(url: str) -> ExtractedDownloadMeta:
urlobj = urlparse(url)
query = parse_qs(urlobj.query)
def get_first_element(elements: list[str]) -> int:
return int(elements[0])
meta = ExtractedDownloadMeta(
mod_time=int(query.get("t")[0]),
min_point_version=int(query.get("minpt")[0]),
max_point_version=int(query.get("maxpt")[0]),
branch_index=int(query.get("bidx")[0]),
mod_time=get_first_element(query["t"]),
min_point_version=get_first_element(query["minpt"]),
max_point_version=get_first_element(query["maxpt"]),
branch_index=get_first_element(query["bidx"]),
)
return meta

View file

@ -6,7 +6,8 @@ from __future__ import annotations
import json
import math
import re
from typing import Callable, Sequence
from collections.abc import Callable, Sequence
from typing import Any
import aqt
import aqt.browser

View file

@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Callable
from collections.abc import Callable
import aqt
from anki.cards import Card, CardId

View file

@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Sequence
from collections.abc import Sequence
import aqt
import aqt.forms

View file

@ -13,6 +13,7 @@ import aqt.forms
from anki.collection import SearchNode
from anki.notes import NoteId
from aqt.qt import *
from aqt.qt import sip
from aqt.webview import AnkiWebViewKind
from ..operations import QueryOp

View file

@ -6,7 +6,8 @@ from __future__ import annotations
import json
import re
import time
from typing import Any, Callable
from collections.abc import Callable
from typing import Any
import aqt.browser
from anki.cards import Card

View file

@ -2,8 +2,8 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from collections.abc import Callable, Iterable
from enum import Enum, auto
from typing import Callable, Iterable
from anki.collection import SearchNode
from aqt.theme import ColoredIcon

View file

@ -3,6 +3,7 @@
from __future__ import annotations
from collections.abc import Callable
from enum import Enum, auto
import aqt

View file

@ -2,8 +2,9 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from collections.abc import Callable, Iterable
from enum import Enum, auto
from typing import Iterable, cast
from typing import cast
import aqt
import aqt.browser
@ -43,6 +44,7 @@ from aqt.operations.tag import (
set_tag_collapsed,
)
from aqt.qt import *
from aqt.qt import sip
from aqt.theme import ColoredIcon, theme_manager
from aqt.utils import (
KeyboardModifiersPressed,
@ -147,7 +149,7 @@ class SidebarTreeView(QTreeView):
def op_executed(
self, changes: OpChanges, handler: object | None, focused: bool
) -> None:
if changes.browser_sidebar and not handler is self:
if changes.browser_sidebar and handler is not self:
self._refresh_needed = True
if focused:
self.refresh_if_needed()
@ -266,7 +268,7 @@ class SidebarTreeView(QTreeView):
def update_search(
self,
*terms: Union[str, SearchNode],
*terms: str | SearchNode,
joiner: SearchJoiner = "AND",
) -> None:
"""Modify the current search string based on modifier keys, then refresh."""
@ -524,7 +526,7 @@ class SidebarTreeView(QTreeView):
*,
root: SidebarItem,
name: str,
icon: Union[str, ColoredIcon],
icon: str | ColoredIcon,
collapse_key: Config.Bool.V,
type: SidebarItemType | None = None,
) -> SidebarItem:

View file

@ -4,8 +4,9 @@ from __future__ import annotations
import copy
import time
from collections.abc import Generator, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Generator, Sequence, Union
from typing import TYPE_CHECKING, Union
import aqt
import aqt.browser

View file

@ -3,7 +3,8 @@
from __future__ import annotations
import time
from typing import Any, Callable, Sequence
from collections.abc import Callable, Sequence
from typing import Any
import aqt
import aqt.browser
@ -242,7 +243,7 @@ class DataModel(QAbstractTableModel):
self._state = self._state.toggle_state()
try:
self._search_inner(context)
except:
except Exception:
# rollback to prevent inconsistent state
self._state = self._state.toggle_state()
raise

View file

@ -3,7 +3,8 @@
from __future__ import annotations
from abc import ABC, abstractmethod, abstractproperty
from typing import Sequence, cast
from collections.abc import Sequence
from typing import cast
from anki.browser import BrowserConfig
from anki.cards import Card, CardId

View file

@ -2,7 +2,8 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from typing import Any, Callable, Sequence
from collections.abc import Callable, Sequence
from typing import Any
import aqt
import aqt.browser

View file

@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Sequence
from collections.abc import Sequence
import aqt
import aqt.deckconf

View file

@ -5,8 +5,9 @@ from __future__ import annotations
import json
import re
from collections.abc import Callable
from concurrent.futures import Future
from typing import Any, Match, Optional, cast
from typing import Any, Match, cast
import aqt
import aqt.forms
@ -50,7 +51,7 @@ class CardLayout(QDialog):
mw: AnkiQt,
note: Note,
ord: int = 0,
parent: Optional[QWidget] = None,
parent: QWidget | None = None,
fill_empty: bool = False,
) -> None:
QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
@ -509,7 +510,7 @@ class CardLayout(QDialog):
# Preview
##########################################################################
_previewTimer: Optional[QTimer] = None
_previewTimer: QTimer | None = None
def renderPreview(self) -> None:
# schedule a preview when timing stops
@ -590,7 +591,7 @@ class CardLayout(QDialog):
return res
type_filter = r"\[\[type:.+?\]\]"
repl: Union[str, Callable]
repl: str | Callable
if type == "q":
repl = "<input id='typeans' type=text value='example' readonly='readonly'>"

View file

@ -3,8 +3,6 @@
from __future__ import annotations
from typing import Tuple
import aqt
import aqt.forms
import aqt.operations
@ -37,12 +35,12 @@ class CustomStudy(QDialog):
def fetch_data_and_show(mw: aqt.AnkiQt) -> None:
def fetch_data(
col: Collection,
) -> Tuple[DeckId, CustomStudyDefaults]:
) -> tuple[DeckId, CustomStudyDefaults]:
deck_id = mw.col.decks.get_current_id()
defaults = col.sched.custom_study_defaults(deck_id)
return (deck_id, defaults)
def show_dialog(data: Tuple[DeckId, CustomStudyDefaults]) -> None:
def show_dialog(data: tuple[DeckId, CustomStudyDefaults]) -> None:
deck_id, defaults = data
CustomStudy(mw=mw, deck_id=deck_id, defaults=defaults)

View file

@ -4,6 +4,8 @@
from __future__ import annotations
import os
import sys
from collections.abc import Callable
from dataclasses import dataclass
from functools import partial
from pathlib import Path
@ -292,7 +294,7 @@ class DebugConsole(QDialog):
try:
# pylint: disable=exec-used
exec(text, vars)
except:
except Exception:
self._output += traceback.format_exc()
self._captureOutput(False)
buf = ""

View file

@ -3,10 +3,13 @@
from __future__ import annotations
from collections.abc import Callable
from anki.collection import OpChanges
from anki.decks import DEFAULT_DECK_ID, DeckId
from aqt import AnkiQt, gui_hooks
from aqt.qt import *
from aqt.qt import sip
from aqt.utils import HelpPage, shortcut, tr

View file

@ -173,8 +173,8 @@ class DeckConf(QDialog):
# Loading
##################################################
def listToUser(self, l: list[Union[int, float]]) -> str:
def num_to_user(n: Union[int, float]) -> str:
def listToUser(self, l: list[int | float]) -> str:
def num_to_user(n: int | float) -> str:
if n == round(n):
return str(int(n))
else:
@ -267,7 +267,7 @@ class DeckConf(QDialog):
if i == int(i):
i = int(i)
ret.append(i)
except:
except Exception:
# invalid, don't update
showWarning(tr.scheduling_steps_must_be_numbers())
return

View file

@ -1,6 +1,8 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from typing import Optional
from __future__ import annotations
from collections.abc import Callable
import aqt.editor
from anki.collection import OpChanges
@ -37,7 +39,7 @@ class EditCurrent(QMainWindow):
self.show()
def on_operation_did_execute(
self, changes: OpChanges, handler: Optional[object]
self, changes: OpChanges, handler: object | None
) -> None:
if changes.note_text and handler is not self.editor:
# reload note

View file

@ -9,14 +9,16 @@ import html
import itertools
import json
import mimetypes
import os
import re
import urllib.error
import urllib.parse
import urllib.request
import warnings
from collections.abc import Callable
from enum import Enum
from random import randrange
from typing import Any, Callable, Match, cast
from typing import Any, Match, cast
import bs4
import requests
@ -59,7 +61,7 @@ from aqt.utils import (
)
from aqt.webview import AnkiWebView, AnkiWebViewKind
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp", "ico", "avif")
pics = ("jpg", "jpeg", "png", "gif", "svg", "webp", "ico", "avif")
audio = (
"3gp",
"aac",
@ -294,6 +296,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
disables: bool = True,
rightside: bool = True,
) -> str:
title_attribute = tip
if icon:
if icon.startswith("qrc:/"):
iconstr = icon
@ -301,47 +305,35 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
iconstr = self.resourceToData(icon)
else:
iconstr = f"/_anki/imgs/{icon}.png"
imgelm = f"""<img class="topbut" src="{iconstr}">"""
image_element = f'<img class="topbut" src="{iconstr}">'
else:
imgelm = ""
if label or not imgelm:
labelelm = label or cmd
image_element = ""
if not label and icon:
label_element = ""
elif label:
label_element = label
else:
labelelm = ""
if id:
idstr = f"id={id}"
else:
idstr = ""
if toggleable:
toggleScript = "toggleEditorButton(this);"
else:
toggleScript = ""
tip = shortcut(tip)
if rightside:
class_ = "linkb"
else:
class_ = "rounded"
label_element = cmd
title_attribute = shortcut(title_attribute)
cmd_to_toggle_button = "toggleEditorButton(this);" if toggleable else ""
id_attribute_assignment = f"id={id}" if id else ""
class_attribute = "linkb" if rightside else "rounded"
if not disables:
class_ += " perm"
return """<button tabindex=-1
{id}
class="{class_}"
class_attribute += " perm"
return f"""<button tabindex=-1
{id_attribute_assignment}
class="{class_attribute}"
type="button"
title="{tip}"
onclick="pycmd('{cmd}');{togglesc}return false;"
title="{title_attribute}"
onclick="pycmd('{cmd}');{cmd_to_toggle_button}return false;"
onmousedown="window.event.preventDefault();"
>
{imgelm}
{labelelm}
</button>""".format(
imgelm=imgelm,
cmd=cmd,
tip=tip,
labelelm=labelelm,
id=idstr,
togglesc=toggleScript,
class_=class_,
)
{image_element}
{label_element}
</button>"""
def setupShortcuts(self) -> None:
# if a third element is provided, enable shortcut even when no field selected

View file

@ -3,15 +3,19 @@
from __future__ import annotations
import os
import re
import sys
import time
from typing import TYPE_CHECKING, Optional, TextIO, cast
import traceback
from typing import TYPE_CHECKING, TextIO, cast
from markdown import markdown
import aqt
from anki.collection import HelpPage
from anki.errors import BackendError, Interrupted
from anki.utils import is_win
from aqt.addons import AddonManager, AddonMeta
from aqt.qt import *
from aqt.utils import openHelp, showWarning, supportText, tooltip, tr
@ -169,7 +173,7 @@ class ErrorHandler(QObject):
def __init__(self, mw: AnkiQt) -> None:
QObject.__init__(self, mw)
self.mw = mw
self.timer: Optional[QTimer] = None
self.timer: QTimer | None = None
qconnect(self.errorTimer, self._setTimer)
self.pool = ""
self._oldstderr = sys.stderr

View file

@ -7,7 +7,6 @@ import os
import re
import time
from concurrent.futures import Future
from typing import Optional
import aqt
import aqt.forms
@ -35,7 +34,7 @@ class ExportDialog(QDialog):
mw: aqt.main.AnkiQt,
did: DeckId | None = None,
cids: list[CardId] | None = None,
parent: Optional[QWidget] = None,
parent: QWidget | None = None,
):
QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
self.mw = mw

View file

@ -3,8 +3,6 @@
from __future__ import annotations
from typing import Optional
import aqt
import aqt.forms
import aqt.operations
@ -32,7 +30,7 @@ class FieldDialog(QDialog):
self,
mw: AnkiQt,
nt: NotetypeDict,
parent: Optional[QWidget] = None,
parent: QWidget | None = None,
open_at: int = 0,
) -> None:
QDialog.__init__(self, parent or mw)
@ -62,7 +60,7 @@ class FieldDialog(QDialog):
self.form.buttonBox.button(QDialogButtonBox.StandardButton.Save).setAutoDefault(
False
)
self.currentIdx: Optional[int] = None
self.currentIdx: int | None = None
self.fillFields()
self.setupSignals()
self.form.fieldList.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
@ -125,8 +123,8 @@ class FieldDialog(QDialog):
self.loadField(idx)
def _uniqueName(
self, prompt: str, ignoreOrd: Optional[int] = None, old: str = ""
) -> Optional[str]:
self, prompt: str, ignoreOrd: int | None = None, old: str = ""
) -> str | None:
txt = getOnlyText(prompt, default=old).replace('"', "").strip()
if not txt:
return None

View file

@ -44,6 +44,9 @@
<property name="text">
<string>preferences_language</string>
</property>
<property name="buddy">
<cstring>lang</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
@ -64,6 +67,9 @@
<property name="text">
<string>preferences_video_driver</string>
</property>
<property name="buddy">
<cstring>video_driver</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
@ -76,6 +82,19 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_for_updates">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>preferences_check_for_updates</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -116,6 +135,9 @@
<property name="text">
<string>preferences_style</string>
</property>
<property name="buddy">
<cstring>styleComboBox</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
@ -123,6 +145,9 @@
<property name="text">
<string>preferences_theme</string>
</property>
<property name="buddy">
<cstring>theme</cstring>
</property>
</widget>
</item>
<item row="2" column="0">
@ -130,6 +155,9 @@
<property name="text">
<string>preferences_user_interface_size</string>
</property>
<property name="buddy">
<cstring>uiScale</cstring>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
@ -264,33 +292,25 @@
<widget class="QSpinBox" name="lrnCutoff">
<property name="maximumSize">
<size>
<width>60</width>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>preferences_mins</string>
</property>
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_29">
<property name="text">
<string>preferences_mins</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_30">
<property name="text">
<string>preferences_timebox_time_limit</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_40">
<property name="text">
<string>preferences_hours_past_midnight</string>
<property name="buddy">
<cstring>timeLimit</cstring>
</property>
</widget>
</item>
@ -298,10 +318,13 @@
<widget class="QSpinBox" name="dayOffset">
<property name="maximumSize">
<size>
<width>60</width>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>preferences_hours_past_midnight</string>
</property>
<property name="maximum">
<number>23</number>
</property>
@ -312,6 +335,9 @@
<property name="text">
<string>preferences_learn_ahead_limit</string>
</property>
<property name="buddy">
<cstring>lrnCutoff</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
@ -319,17 +345,16 @@
<property name="text">
<string>preferences_next_day_starts_at</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_39">
<property name="text">
<string>preferences_mins</string>
<property name="buddy">
<cstring>dayOffset</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="timeLimit">
<property name="suffix">
<string>preferences_mins</string>
</property>
<property name="maximum">
<number>9999</number>
</property>
@ -513,6 +538,9 @@
<property name="text">
<string>preferences_default_deck</string>
</property>
<property name="buddy">
<cstring>useCurrent</cstring>
</property>
</widget>
</item>
<item>
@ -553,6 +581,9 @@
<property name="text">
<string>preferences_default_search_text</string>
</property>
<property name="buddy">
<cstring>default_search_text</cstring>
</property>
</widget>
</item>
<item>
@ -705,10 +736,16 @@
<property name="text">
<string>preferences_network_timeout</string>
</property>
<property name="buddy">
<cstring>network_timeout</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="network_timeout">
<property name="suffix">
<string>scheduling_seconds</string>
</property>
<property name="minimum">
<number>30</number>
</property>
@ -717,26 +754,6 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>scheduling_seconds</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
@ -853,6 +870,9 @@
<property name="text">
<string>preferences_custom_sync_url</string>
</property>
<property name="buddy">
<cstring>custom_sync_url</cstring>
</property>
</widget>
</item>
</layout>
@ -954,6 +974,9 @@
<property name="text">
<string>preferences_daily_backups</string>
</property>
<property name="buddy">
<cstring>daily_backups</cstring>
</property>
</widget>
</item>
<item row="4" column="2">
@ -968,6 +991,9 @@
<property name="text">
<string>preferences_monthly_backups</string>
</property>
<property name="buddy">
<cstring>monthly_backups</cstring>
</property>
</widget>
</item>
<item row="4" column="0">
@ -975,6 +1001,9 @@
<property name="text">
<string>preferences_weekly_backups</string>
</property>
<property name="buddy">
<cstring>weekly_backups</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
@ -982,6 +1011,9 @@
<property name="text">
<string>preferences_minutes_between_backups</string>
</property>
<property name="buddy">
<cstring>minutes_between_backups</cstring>
</property>
</widget>
</item>
<item row="2" column="2">

View file

@ -7,8 +7,8 @@ import os
import re
import time
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Optional, Sequence, Type
import aqt.forms
import aqt.main
@ -42,7 +42,7 @@ class ExportDialog(QDialog):
mw: aqt.main.AnkiQt,
did: DeckId | None = None,
nids: Sequence[NoteId] | None = None,
parent: Optional[QWidget] = None,
parent: QWidget | None = None,
):
QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
self.mw = mw
@ -56,7 +56,7 @@ class ExportDialog(QDialog):
self.open()
def setup(self, did: DeckId | None) -> None:
self.exporter_classes: list[Type[Exporter]] = [
self.exporter_classes: list[type[Exporter]] = [
ApkgExporter,
ColpkgExporter,
NoteCsvExporter,

View file

@ -3,10 +3,11 @@
from __future__ import annotations
import os
import re
from abc import ABC, abstractmethod
from collections.abc import Callable
from itertools import chain
from typing import Type
import aqt.main
from anki.collection import Collection, Progress
@ -124,7 +125,7 @@ class JsonImporter(Importer):
ImportDialog(mw, JsonFileArgs(path=path))
IMPORTERS: list[Type[Importer]] = [
IMPORTERS: list[type[Importer]] = [
ColpkgImporter,
ApkgImporter,
MnemosyneImporter,

View file

@ -1,11 +1,15 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
import os
import re
import sys
import traceback
import zipfile
from collections.abc import Callable
from concurrent.futures import Future
from typing import Any, Optional
from typing import Any
import anki.importing as importing
import aqt.deckchooser
@ -54,7 +58,7 @@ class ChangeMap(QDialog):
self.frm.fields.setCurrentRow(n)
else:
self.frm.fields.setCurrentRow(n + 1)
self.field: Optional[str] = None
self.field: str | None = None
def getField(self) -> str:
self.exec()
@ -230,13 +234,13 @@ class ImportDialog(QDialog):
self.frm.mappingArea.setWidget(self.frame)
self.mapbox = QVBoxLayout(self.frame)
self.mapbox.setContentsMargins(0, 0, 0, 0)
self.mapwidget: Optional[QWidget] = None
self.mapwidget: QWidget | None = None
def hideMapping(self) -> None:
self.frm.mappingGroup.hide()
def showMapping(
self, keepMapping: bool = False, hook: Optional[Callable] = None
self, keepMapping: bool = False, hook: Callable | None = None
) -> None:
if hook:
hook()

View file

@ -8,10 +8,13 @@ import gc
import os
import re
import signal
import sys
import traceback
import weakref
from argparse import Namespace
from collections.abc import Callable, Sequence
from concurrent.futures import Future
from typing import Any, Literal, Sequence, TypeVar, cast
from typing import Any, Literal, TypeVar, cast
import anki
import anki.cards
@ -199,7 +202,7 @@ class AnkiQt(QMainWindow):
self.setupUI()
self.setupAddons(args)
self.finish_ui_setup()
except:
except Exception:
showInfo(tr.qt_misc_error_during_startup(val=traceback.format_exc()))
sys.exit(1)
# must call this after ui set up
@ -350,7 +353,7 @@ class AnkiQt(QMainWindow):
f.profiles.addItems(profs)
try:
idx = profs.index(self.pm.name)
except:
except Exception:
idx = 0
f.profiles.setCurrentRow(idx)
@ -680,7 +683,7 @@ class AnkiQt(QMainWindow):
self.maybeOptimize()
if not dev_mode:
corrupt = self.col.db.scalar("pragma quick_check") != "ok"
except:
except Exception:
corrupt = True
try:
@ -692,7 +695,7 @@ class AnkiQt(QMainWindow):
force=False,
wait_for_completion=False,
)
except:
except Exception:
print("backup on close failed")
self.col.close(downgrade=False)
except Exception as e:
@ -1428,6 +1431,7 @@ title="{}" {}>{}</button>""".format(
def setup_auto_update(self, _log: list[DownloadLogEntry]) -> None:
from aqt.update import check_for_update
if aqt.mw.pm.check_for_updates():
check_for_update()
# Timers

View file

@ -5,8 +5,9 @@ from __future__ import annotations
import itertools
import time
from collections.abc import Iterable, Sequence
from concurrent.futures import Future
from typing import Iterable, Sequence, TypeVar
from typing import TypeVar
import aqt
import aqt.progress

View file

@ -11,10 +11,10 @@ import re
import sys
import threading
import traceback
from collections.abc import Callable
from dataclasses import dataclass
from errno import EPROTOTYPE
from http import HTTPStatus
from typing import Callable
import flask
import flask_cors

View file

@ -4,9 +4,10 @@
from __future__ import annotations
import time
from collections.abc import Callable
from concurrent.futures import Future
from datetime import datetime
from typing import Any, Callable
from typing import Any
import aqt
import aqt.forms

View file

@ -1,7 +1,8 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from typing import Optional
from collections.abc import Callable
from aqt import AnkiQt, gui_hooks
from aqt.qt import *
@ -16,7 +17,7 @@ class ModelChooser(QHBoxLayout):
mw: AnkiQt,
widget: QWidget,
label: bool = True,
on_activated: Optional[Callable[[], None]] = None,
on_activated: Callable[[], None] | None = None,
) -> None:
"""If provided, on_activated() will be called when the button is clicked,
and the caller can call .onModelChange() to pull up the dialog when they

View file

@ -3,9 +3,10 @@
from __future__ import annotations
from collections.abc import Callable, Sequence
from concurrent.futures import Future
from operator import itemgetter
from typing import Any, Optional, Sequence
from typing import Any
import aqt.clayout
from anki import stdmodels
@ -40,9 +41,9 @@ class Models(QDialog):
def __init__(
self,
mw: AnkiQt,
parent: Optional[QWidget] = None,
parent: QWidget | None = None,
fromMain: bool = False,
selected_notetype_id: Optional[NotetypeId] = None,
selected_notetype_id: NotetypeId | None = None,
):
self.mw = mw
parent = parent or mw
@ -61,6 +62,13 @@ class Models(QDialog):
self.models: Sequence[NotetypeNameIdUseCount] = []
self.setupModels()
restoreGeom(self, "models")
self.setWindowFlags(
self.windowFlags()
| Qt.WindowType.WindowMaximizeButtonHint
| Qt.WindowType.WindowMinimizeButtonHint
)
self.show()
# Models
@ -231,13 +239,13 @@ class Models(QDialog):
class AddModel(QDialog):
model: Optional[NotetypeDict]
model: NotetypeDict | None
def __init__(
self,
mw: AnkiQt,
on_success: Callable[[NotetypeDict], None],
parent: Optional[QWidget] = None,
parent: QWidget | None = None,
) -> None:
self.parent_ = parent or mw
self.mw = mw
@ -249,9 +257,7 @@ class AddModel(QDialog):
self.setWindowModality(Qt.WindowModality.ApplicationModal)
disable_help_button(self)
# standard models
self.notetypes: list[
Union[NotetypeDict, Callable[[Collection], NotetypeDict]]
] = []
self.notetypes: list[NotetypeDict | Callable[[Collection], NotetypeDict]] = []
for name, func in stdmodels.get_stock_notetypes(self.col):
item = QListWidgetItem(tr.notetypes_add(val=name))
self.dialog.models.addItem(item)

View file

@ -25,6 +25,7 @@
# ------------------------------------------------------------------------------
# pylint: disable=raise-missing-from
from __future__ import annotations
import inspect
import json
@ -38,7 +39,6 @@ import threading
import time
from queue import Empty, Full, Queue
from shutil import which
from typing import Optional
from anki.utils import is_win
@ -77,7 +77,7 @@ class MPVBase:
"""
executable = which("mpv")
popenEnv: Optional[dict[str, str]] = None
popenEnv: dict[str, str] | None = None
default_argv = [
"--idle",

View file

@ -3,6 +3,8 @@
from __future__ import annotations
from collections.abc import Callable
from anki.collection import OpChanges
from anki.models import NotetypeId
from aqt import AnkiQt, gui_hooks

View file

@ -3,8 +3,9 @@
from __future__ import annotations
from collections.abc import Callable
from concurrent.futures._base import Future
from typing import Any, Callable, Generic, Protocol, TypeVar, Union
from typing import Any, Generic, Protocol, TypeVar, Union
import aqt
import aqt.gui_hooks

View file

@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Sequence
from collections.abc import Sequence
from anki.cards import CardId
from anki.collection import OpChangesWithCount

Some files were not shown because too many files have changed in this diff Show more