start work on more clearly defining backend/protobuf boundaries
- anki._backend stores the protobuf files and rsbackend.py code - pylib modules import protobuf messages directly from the _pb2 files, and explicitly export any will be returned or consumed by public pylib functions, so that calling code can import from pylib - the "rsbackend" no longer imports and re-exports protobuf messages - pylib can just consume them directly. - move errors to errors.py Still todo: - rsbridge - finishing the work on rsbackend, and check what we need to add back to the original file location to avoid breaking add-ons
This commit is contained in:
parent
cd9767be80
commit
9d853bbb03
|
@ -1,5 +1,5 @@
|
|||
[settings]
|
||||
skip=aqt/forms,backend_pb2.py,backend_pb2.pyi,fluent_pb2.py,fluent_pb2.pyi,rsbackend_gen.py,hooks_gen.py,genbackend.py
|
||||
skip=aqt/forms,backend_pb2.py,backend_pb2.pyi,fluent_pb2.py,fluent_pb2.pyi,rsbackend_gen.py,generated.py,hooks_gen.py,genbackend.py
|
||||
profile=black
|
||||
multi_line_output=3
|
||||
include_trailing_comma=True
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
|
||||
load("@rules_python//python:defs.bzl", "py_library")
|
||||
load("@py_deps//:requirements.bzl", "requirement")
|
||||
load("//pylib:protobuf.bzl", "py_proto_library_typed")
|
||||
load("@rules_python//experimental/python:wheel.bzl", "py_package", "py_wheel")
|
||||
load("@bazel_skylib//lib:selects.bzl", "selects")
|
||||
load("//:defs.bzl", "anki_version")
|
||||
|
@ -13,13 +12,6 @@ copy_file(
|
|||
out = "buildinfo.txt",
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "rsbackend_gen",
|
||||
outs = ["rsbackend_gen.py"],
|
||||
cmd = "$(location //pylib/tools:genbackend) > $@",
|
||||
tools = ["//pylib/tools:genbackend"],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "hooks_gen",
|
||||
outs = ["hooks_gen.py"],
|
||||
|
@ -27,22 +19,6 @@ genrule(
|
|||
tools = ["//pylib/tools:genhooks"],
|
||||
)
|
||||
|
||||
py_proto_library_typed(
|
||||
name = "backend_pb2",
|
||||
src = "//rslib:backend.proto",
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
||||
py_proto_library_typed(
|
||||
name = "fluent_pb2",
|
||||
src = "//rslib:fluent.proto",
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
||||
copy_file(
|
||||
name = "rsbridge_unix",
|
||||
src = "//pylib/rsbridge",
|
||||
|
@ -69,7 +45,6 @@ alias(
|
|||
_py_srcs = glob(
|
||||
["**/*.py"],
|
||||
exclude = [
|
||||
"rsbackend_gen.py",
|
||||
"hooks_gen.py",
|
||||
],
|
||||
)
|
||||
|
@ -79,12 +54,10 @@ py_library(
|
|||
srcs = _py_srcs,
|
||||
data = [
|
||||
"py.typed",
|
||||
":backend_pb2",
|
||||
":buildinfo",
|
||||
":fluent_pb2",
|
||||
":hooks_gen",
|
||||
":rsbackend_gen",
|
||||
":rsbridge",
|
||||
"//pylib/anki/_backend",
|
||||
],
|
||||
imports = [
|
||||
"..",
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
load("@rules_python//python:defs.bzl", "py_binary")
|
||||
load("@py_deps//:requirements.bzl", "requirement")
|
||||
load("//pylib:protobuf.bzl", "py_proto_library_typed")
|
||||
|
||||
py_proto_library_typed(
|
||||
name = "backend_pb2",
|
||||
src = "//rslib:backend.proto",
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
||||
py_proto_library_typed(
|
||||
name = "fluent_pb2",
|
||||
src = "//rslib:fluent.proto",
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "genbackend",
|
||||
srcs = [
|
||||
"backend_pb2",
|
||||
"genbackend.py",
|
||||
],
|
||||
deps = [
|
||||
requirement("black"),
|
||||
requirement("stringcase"),
|
||||
requirement("protobuf"),
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "rsbackend_gen",
|
||||
outs = ["generated.py"],
|
||||
cmd = "$(location genbackend) > $@",
|
||||
tools = ["genbackend"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "_backend",
|
||||
srcs = [
|
||||
"__init__.py",
|
||||
":backend_pb2",
|
||||
":fluent_pb2",
|
||||
":rsbackend_gen",
|
||||
],
|
||||
visibility = ["//pylib:__subpackages__"],
|
||||
)
|
|
@ -22,19 +22,19 @@ import os
|
|||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union
|
||||
|
||||
import anki._backend.backend_pb2 as pb
|
||||
import anki._rsbridge
|
||||
import anki.backend_pb2 as pb
|
||||
import anki.buildinfo
|
||||
from anki import hooks
|
||||
from anki._backend.generated import RustBackendGenerated
|
||||
from anki.dbproxy import Row as DBRow
|
||||
from anki.dbproxy import ValueForDB
|
||||
from anki.fluent_pb2 import FluentString as TR
|
||||
from anki.rsbackend_gen import RustBackendGenerated
|
||||
from anki.errors import backend_exception_to_pylib
|
||||
from anki.lang import FormatTimeSpanContext
|
||||
from anki.utils import from_json_bytes, to_json_bytes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anki.fluent_pb2 import FluentStringValue as TRValue
|
||||
|
||||
FormatTimeSpanContextValue = pb.FormatTimespanIn.ContextValue
|
||||
from anki.lang import FormatTimeSpanContextValue, TRValue
|
||||
|
||||
assert anki._rsbridge.buildhash() == anki.buildinfo.buildhash
|
||||
|
||||
|
@ -48,151 +48,10 @@ BackendNote = pb.Note
|
|||
Tag = pb.Tag
|
||||
TagTreeNode = pb.TagTreeNode
|
||||
NoteType = pb.NoteType
|
||||
DeckTreeNode = pb.DeckTreeNode
|
||||
StockNoteType = pb.StockNoteType
|
||||
ConcatSeparator = pb.ConcatenateSearchesIn.Separator
|
||||
SyncAuth = pb.SyncAuth
|
||||
SyncOutput = pb.SyncCollectionOut
|
||||
SyncStatus = pb.SyncStatusOut
|
||||
CountsForDeckToday = pb.CountsForDeckTodayOut
|
||||
|
||||
try:
|
||||
import orjson
|
||||
|
||||
to_json_bytes = orjson.dumps
|
||||
from_json_bytes = orjson.loads
|
||||
except:
|
||||
print("orjson is missing; DB operations will be slower")
|
||||
to_json_bytes = lambda obj: json.dumps(obj).encode("utf8") # type: ignore
|
||||
from_json_bytes = json.loads
|
||||
|
||||
|
||||
class Interrupted(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class StringError(Exception):
|
||||
def __str__(self) -> str:
|
||||
return self.args[0] # pylint: disable=unsubscriptable-object
|
||||
|
||||
|
||||
NetworkErrorKind = pb.NetworkError.NetworkErrorKind
|
||||
SyncErrorKind = pb.SyncError.SyncErrorKind
|
||||
|
||||
|
||||
class NetworkError(StringError):
|
||||
def kind(self) -> pb.NetworkError.NetworkErrorKindValue:
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class SyncError(StringError):
|
||||
def kind(self) -> pb.SyncError.SyncErrorKindValue:
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class IOError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class DBError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class TemplateError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ExistsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DeckIsFilteredError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidInput(StringError):
|
||||
pass
|
||||
|
||||
|
||||
def proto_exception_to_native(err: pb.BackendError) -> Exception:
|
||||
val = err.WhichOneof("value")
|
||||
if val == "interrupted":
|
||||
return Interrupted()
|
||||
elif val == "network_error":
|
||||
return NetworkError(err.localized, err.network_error.kind)
|
||||
elif val == "sync_error":
|
||||
return SyncError(err.localized, err.sync_error.kind)
|
||||
elif val == "io_error":
|
||||
return IOError(err.localized)
|
||||
elif val == "db_error":
|
||||
return DBError(err.localized)
|
||||
elif val == "template_parse":
|
||||
return TemplateError(err.localized)
|
||||
elif val == "invalid_input":
|
||||
return InvalidInput(err.localized)
|
||||
elif val == "json_error":
|
||||
return StringError(err.localized)
|
||||
elif val == "not_found_error":
|
||||
return NotFoundError()
|
||||
elif val == "exists":
|
||||
return ExistsError()
|
||||
elif val == "deck_is_filtered":
|
||||
return DeckIsFilteredError()
|
||||
elif val == "proto_error":
|
||||
return StringError(err.localized)
|
||||
else:
|
||||
print("unhandled error type:", val)
|
||||
return StringError(err.localized)
|
||||
|
||||
|
||||
MediaSyncProgress = pb.MediaSyncProgress
|
||||
FullSyncProgress = pb.FullSyncProgress
|
||||
NormalSyncProgress = pb.NormalSyncProgress
|
||||
DatabaseCheckProgress = pb.DatabaseCheckProgress
|
||||
|
||||
FormatTimeSpanContext = pb.FormatTimespanIn.Context
|
||||
|
||||
|
||||
class ProgressKind(enum.Enum):
|
||||
NoProgress = 0
|
||||
MediaSync = 1
|
||||
MediaCheck = 2
|
||||
FullSync = 3
|
||||
NormalSync = 4
|
||||
DatabaseCheck = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
class Progress:
|
||||
kind: ProgressKind
|
||||
val: Union[
|
||||
MediaSyncProgress,
|
||||
pb.FullSyncProgress,
|
||||
NormalSyncProgress,
|
||||
DatabaseCheckProgress,
|
||||
str,
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def from_proto(proto: pb.Progress) -> Progress:
|
||||
kind = proto.WhichOneof("value")
|
||||
if kind == "media_sync":
|
||||
return Progress(kind=ProgressKind.MediaSync, val=proto.media_sync)
|
||||
elif kind == "media_check":
|
||||
return Progress(kind=ProgressKind.MediaCheck, val=proto.media_check)
|
||||
elif kind == "full_sync":
|
||||
return Progress(kind=ProgressKind.FullSync, val=proto.full_sync)
|
||||
elif kind == "normal_sync":
|
||||
return Progress(kind=ProgressKind.NormalSync, val=proto.normal_sync)
|
||||
elif kind == "database_check":
|
||||
return Progress(kind=ProgressKind.DatabaseCheck, val=proto.database_check)
|
||||
else:
|
||||
return Progress(kind=ProgressKind.NoProgress, val="")
|
||||
|
||||
|
||||
class RustBackend(RustBackendGenerated):
|
||||
def __init__(
|
||||
|
@ -240,7 +99,7 @@ class RustBackend(RustBackendGenerated):
|
|||
err_bytes = bytes(e.args[0])
|
||||
err = pb.BackendError()
|
||||
err.ParseFromString(err_bytes)
|
||||
raise proto_exception_to_native(err)
|
||||
raise backend_exception_to_pylib(err)
|
||||
|
||||
def translate(self, key: TRValue, **kwargs: Union[str, int, float]) -> str:
|
||||
return self.translate_string(translate_string_in(key, **kwargs))
|
||||
|
@ -263,7 +122,7 @@ class RustBackend(RustBackendGenerated):
|
|||
err_bytes = bytes(e.args[0])
|
||||
err = pb.BackendError()
|
||||
err.ParseFromString(err_bytes)
|
||||
raise proto_exception_to_native(err)
|
||||
raise backend_exception_to_pylib(err)
|
||||
|
||||
|
||||
def translate_string_in(
|
|
@ -0,0 +1 @@
|
|||
../../../bazel-bin/pylib/anki/_backend/backend_pb2.pyi
|
|
@ -0,0 +1 @@
|
|||
../../../bazel-bin/pylib/anki/_backend/fluent_pb2.pyi
|
|
@ -5,7 +5,7 @@ import os
|
|||
import re
|
||||
import sys
|
||||
|
||||
import pylib.anki.backend_pb2 as pb
|
||||
import pylib.anki._backend.backend_pb2 as pb
|
||||
|
||||
import stringcase
|
||||
|
||||
|
@ -168,7 +168,7 @@ col.decks.all_config()
|
|||
|
||||
from typing import *
|
||||
|
||||
import anki.backend_pb2 as pb
|
||||
import anki._backend.backend_pb2 as pb
|
||||
|
||||
class RustBackendGenerated:
|
||||
def _run_command(self, method: int, input: Any) -> bytes:
|
|
@ -0,0 +1 @@
|
|||
../../../bazel-bin/pylib/anki/_backend/generated.py
|
|
@ -1 +0,0 @@
|
|||
../../bazel-bin/pylib/anki/backend_pb2.pyi
|
|
@ -8,11 +8,11 @@ import time
|
|||
from typing import List, Optional
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki import hooks
|
||||
from anki.consts import *
|
||||
from anki.models import NoteType, Template
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import BackendCard
|
||||
from anki.sound import AVTag
|
||||
|
||||
# Cards
|
||||
|
@ -45,14 +45,14 @@ class Card:
|
|||
self.load()
|
||||
else:
|
||||
# new card with defaults
|
||||
self._load_from_backend_card(BackendCard())
|
||||
self._load_from_backend_card(_pb.Card())
|
||||
|
||||
def load(self) -> None:
|
||||
c = self.col.backend.get_card(self.id)
|
||||
assert c
|
||||
self._load_from_backend_card(c)
|
||||
|
||||
def _load_from_backend_card(self, c: BackendCard) -> None:
|
||||
def _load_from_backend_card(self, c: _pb.Card) -> None:
|
||||
self._render_output = None
|
||||
self._note = None
|
||||
self.id = c.id
|
||||
|
@ -86,7 +86,7 @@ class Card:
|
|||
self._bugcheck()
|
||||
hooks.card_will_flush(self)
|
||||
# mtime & usn are set by backend
|
||||
card = BackendCard(
|
||||
card = _pb.Card(
|
||||
id=self.id,
|
||||
note_id=self.nid,
|
||||
deck_id=self.did,
|
||||
|
|
|
@ -4,51 +4,66 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import enum
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import weakref
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
import anki.backend_pb2 as pb
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
import anki.find
|
||||
import anki.latex # sets up hook
|
||||
import anki.template
|
||||
from anki import hooks
|
||||
from anki.backend_pb2 import SearchTerm
|
||||
from anki._backend import ( # pylint: disable=unused-import
|
||||
ConcatSeparator,
|
||||
FormatTimeSpanContext,
|
||||
RustBackend,
|
||||
)
|
||||
|
||||
# from anki._backend import _SyncStatus as SyncStatus
|
||||
from anki.cards import Card
|
||||
from anki.config import ConfigManager
|
||||
from anki.consts import *
|
||||
from anki.dbproxy import DBProxy
|
||||
from anki.decks import DeckManager
|
||||
from anki.errors import AnkiError
|
||||
from anki.errors import AnkiError, DBError
|
||||
from anki.lang import TR
|
||||
from anki.media import MediaManager, media_paths_from_col_path
|
||||
from anki.models import ModelManager
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import ( # pylint: disable=unused-import
|
||||
TR,
|
||||
ConcatSeparator,
|
||||
DBError,
|
||||
FormatTimeSpanContext,
|
||||
InvalidInput,
|
||||
Progress,
|
||||
RustBackend,
|
||||
from_json_bytes,
|
||||
pb,
|
||||
)
|
||||
from anki.sched import Scheduler as V1Scheduler
|
||||
from anki.schedv2 import Scheduler as V2Scheduler
|
||||
from anki.tags import TagManager
|
||||
from anki.utils import devMode, ids2str, intTime, splitFields, stripHTMLMedia
|
||||
from anki.utils import (
|
||||
devMode,
|
||||
from_json_bytes,
|
||||
ids2str,
|
||||
intTime,
|
||||
splitFields,
|
||||
stripHTMLMedia,
|
||||
)
|
||||
|
||||
ConfigBoolKey = pb.ConfigBool.Key # pylint: disable=no-member
|
||||
# public exports
|
||||
SearchTerm = _pb.SearchTerm
|
||||
MediaSyncProgress = _pb.MediaSyncProgress
|
||||
FullSyncProgress = _pb.FullSyncProgress
|
||||
NormalSyncProgress = _pb.NormalSyncProgress
|
||||
DatabaseCheckProgress = _pb.DatabaseCheckProgress
|
||||
ConfigBoolKey = _pb.ConfigBool.Key # pylint: disable=no-member
|
||||
EmptyCardsReport = _pb.EmptyCardsReport
|
||||
NoteWithEmptyCards = _pb.NoteWithEmptyCards
|
||||
GraphPreferences = _pb.GraphPreferences
|
||||
|
||||
# pylint: disable=no-member
|
||||
if TYPE_CHECKING:
|
||||
from anki.rsbackend import FormatTimeSpanContextValue, TRValue
|
||||
from anki.lang import FormatTimeSpanContextValue, TRValue
|
||||
|
||||
ConfigBoolKeyValue = pb.ConfigBool.KeyValue # pylint: disable=no-member
|
||||
ConfigBoolKeyValue = _pb.ConfigBool.KeyValue
|
||||
|
||||
|
||||
class Collection:
|
||||
|
@ -394,6 +409,9 @@ class Collection:
|
|||
def set_deck(self, card_ids: List[int], deck_id: int) -> None:
|
||||
self.backend.set_deck(card_ids=card_ids, deck_id=deck_id)
|
||||
|
||||
def get_empty_cards(self) -> EmptyCardsReport:
|
||||
return self.backend.get_empty_cards()
|
||||
|
||||
# legacy
|
||||
|
||||
def remCards(self, ids: List[int], notes: bool = True) -> None:
|
||||
|
@ -445,20 +463,20 @@ class Collection:
|
|||
order: Union[
|
||||
bool,
|
||||
str,
|
||||
pb.BuiltinSearchOrder.BuiltinSortKindValue, # pylint: disable=no-member
|
||||
_pb.BuiltinSearchOrder.BuiltinSortKindValue, # pylint: disable=no-member
|
||||
] = False,
|
||||
reverse: bool = False,
|
||||
) -> Sequence[int]:
|
||||
if isinstance(order, str):
|
||||
mode = pb.SortOrder(custom=order)
|
||||
mode = _pb.SortOrder(custom=order)
|
||||
elif isinstance(order, bool):
|
||||
if order is True:
|
||||
mode = pb.SortOrder(from_config=pb.Empty())
|
||||
mode = _pb.SortOrder(from_config=_pb.Empty())
|
||||
else:
|
||||
mode = pb.SortOrder(none=pb.Empty())
|
||||
mode = _pb.SortOrder(none=_pb.Empty())
|
||||
else:
|
||||
mode = pb.SortOrder(
|
||||
builtin=pb.BuiltinSearchOrder(kind=order, reverse=reverse)
|
||||
mode = _pb.SortOrder(
|
||||
builtin=_pb.BuiltinSearchOrder(kind=order, reverse=reverse)
|
||||
)
|
||||
return self.backend.search_cards(search=query, order=mode)
|
||||
|
||||
|
@ -603,6 +621,19 @@ table.review-log {{ {revlog_style} }}
|
|||
def studied_today(self) -> str:
|
||||
return self.backend.studied_today()
|
||||
|
||||
def graph_data(self, search: str, days: int) -> bytes:
|
||||
return self.backend.graphs(search=search, days=days)
|
||||
|
||||
def get_graph_preferences(self) -> bytes:
|
||||
return self.backend.get_graph_preferences()
|
||||
|
||||
def set_graph_preferences(self, prefs: GraphPreferences) -> None:
|
||||
self.backend.set_graph_preferences(input=prefs)
|
||||
|
||||
def congrats_info(self) -> bytes:
|
||||
"Don't use this, it will likely go away in the future."
|
||||
return self.backend.congrats_info().SerializeToString()
|
||||
|
||||
# legacy
|
||||
|
||||
def cardStats(self, card: Card) -> str:
|
||||
|
@ -797,5 +828,42 @@ table.review-log {{ {revlog_style} }}
|
|||
)
|
||||
|
||||
|
||||
class ProgressKind(enum.Enum):
|
||||
NoProgress = 0
|
||||
MediaSync = 1
|
||||
MediaCheck = 2
|
||||
FullSync = 3
|
||||
NormalSync = 4
|
||||
DatabaseCheck = 5
|
||||
|
||||
|
||||
@dataclass
|
||||
class Progress:
|
||||
kind: ProgressKind
|
||||
val: Union[
|
||||
MediaSyncProgress,
|
||||
FullSyncProgress,
|
||||
NormalSyncProgress,
|
||||
DatabaseCheckProgress,
|
||||
str,
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def from_proto(proto: _pb.Progress) -> Progress:
|
||||
kind = proto.WhichOneof("value")
|
||||
if kind == "media_sync":
|
||||
return Progress(kind=ProgressKind.MediaSync, val=proto.media_sync)
|
||||
elif kind == "media_check":
|
||||
return Progress(kind=ProgressKind.MediaCheck, val=proto.media_check)
|
||||
elif kind == "full_sync":
|
||||
return Progress(kind=ProgressKind.FullSync, val=proto.full_sync)
|
||||
elif kind == "normal_sync":
|
||||
return Progress(kind=ProgressKind.NormalSync, val=proto.normal_sync)
|
||||
elif kind == "database_check":
|
||||
return Progress(kind=ProgressKind.DatabaseCheck, val=proto.database_check)
|
||||
else:
|
||||
return Progress(kind=ProgressKind.NoProgress, val="")
|
||||
|
||||
|
||||
# legacy name
|
||||
_Collection = Collection
|
||||
|
|
|
@ -23,7 +23,8 @@ import weakref
|
|||
from typing import Any
|
||||
|
||||
import anki
|
||||
from anki.rsbackend import NotFoundError, from_json_bytes, to_json_bytes
|
||||
from anki.errors import NotFoundError
|
||||
from anki.utils import from_json_bytes, to_json_bytes
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
from typing import Any, Dict, Optional
|
||||
|
||||
import anki
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
|
||||
# whether new cards should be mixed with reviews, or shown first or last
|
||||
NEW_CARDS_DISTRIBUTE = 0
|
||||
|
|
|
@ -21,7 +21,7 @@ class DBProxy:
|
|||
# Lifecycle
|
||||
###############
|
||||
|
||||
def __init__(self, backend: anki.rsbackend.RustBackend) -> None:
|
||||
def __init__(self, backend: anki._backend.RustBackend) -> None:
|
||||
self._backend = backend
|
||||
self.mod = False
|
||||
self.last_begin_at = 0
|
||||
|
|
|
@ -8,17 +8,14 @@ import pprint
|
|||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.backend_pb2 as pb
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki.consts import *
|
||||
from anki.errors import DeckRenameError
|
||||
from anki.rsbackend import (
|
||||
TR,
|
||||
DeckTreeNode,
|
||||
NotFoundError,
|
||||
from_json_bytes,
|
||||
to_json_bytes,
|
||||
)
|
||||
from anki.utils import ids2str, intTime
|
||||
from anki.errors import DeckIsFilteredError, DeckRenameError, NotFoundError
|
||||
from anki.utils import from_json_bytes, ids2str, intTime, to_json_bytes
|
||||
|
||||
# public exports
|
||||
DeckTreeNode = _pb.DeckTreeNode
|
||||
DeckNameID = _pb.DeckNameID
|
||||
|
||||
# legacy code may pass this in as the type argument to .id()
|
||||
defaultDeck = 0
|
||||
|
@ -139,7 +136,7 @@ class DeckManager:
|
|||
|
||||
def all_names_and_ids(
|
||||
self, skip_empty_default=False, include_filtered=True
|
||||
) -> Sequence[pb.DeckNameID]:
|
||||
) -> Sequence[DeckNameID]:
|
||||
"A sorted sequence of deck names and IDs."
|
||||
return self.col.backend.get_deck_names(
|
||||
skip_empty_default=skip_empty_default, include_filtered=include_filtered
|
||||
|
@ -166,7 +163,7 @@ class DeckManager:
|
|||
def new_deck_legacy(self, filtered: bool) -> Deck:
|
||||
return from_json_bytes(self.col.backend.new_deck_legacy(filtered))
|
||||
|
||||
def deck_tree(self) -> pb.DeckTreeNode:
|
||||
def deck_tree(self) -> DeckTreeNode:
|
||||
return self.col.backend.deck_tree(top_deck_id=0, now=0)
|
||||
|
||||
@classmethod
|
||||
|
@ -250,7 +247,7 @@ class DeckManager:
|
|||
g["id"] = self.col.backend.add_or_update_deck_legacy(
|
||||
deck=to_json_bytes(g), preserve_usn_and_mtime=preserve_usn
|
||||
)
|
||||
except anki.rsbackend.DeckIsFilteredError as exc:
|
||||
except DeckIsFilteredError as exc:
|
||||
raise DeckRenameError("deck was filtered") from exc
|
||||
|
||||
def rename(self, g: Deck, newName: str) -> None:
|
||||
|
|
|
@ -1,8 +1,92 @@
|
|||
# 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 Any
|
||||
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
|
||||
# fixme: notfounderror etc need to be in rsbackend.py
|
||||
|
||||
|
||||
class StringError(Exception):
|
||||
def __str__(self) -> str:
|
||||
return self.args[0] # pylint: disable=unsubscriptable-object
|
||||
|
||||
|
||||
class Interrupted(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NetworkError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class SyncError(StringError):
|
||||
# pylint: disable=no-member
|
||||
def is_auth_error(self) -> bool:
|
||||
return self.args[1] == _pb.SyncError.SyncErrorKind.AUTH_FAILED
|
||||
|
||||
|
||||
class IOError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class DBError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class TemplateError(StringError):
|
||||
pass
|
||||
|
||||
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ExistsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DeckIsFilteredError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidInput(StringError):
|
||||
pass
|
||||
|
||||
|
||||
def backend_exception_to_pylib(err: _pb.BackendError) -> Exception:
|
||||
val = err.WhichOneof("value")
|
||||
if val == "interrupted":
|
||||
return Interrupted()
|
||||
elif val == "network_error":
|
||||
return NetworkError(err.localized, err.network_error.kind)
|
||||
elif val == "sync_error":
|
||||
return SyncError(err.localized, err.sync_error.kind)
|
||||
elif val == "io_error":
|
||||
return IOError(err.localized)
|
||||
elif val == "db_error":
|
||||
return DBError(err.localized)
|
||||
elif val == "template_parse":
|
||||
return TemplateError(err.localized)
|
||||
elif val == "invalid_input":
|
||||
return InvalidInput(err.localized)
|
||||
elif val == "json_error":
|
||||
return StringError(err.localized)
|
||||
elif val == "not_found_error":
|
||||
return NotFoundError()
|
||||
elif val == "exists":
|
||||
return ExistsError()
|
||||
elif val == "deck_is_filtered":
|
||||
return DeckIsFilteredError()
|
||||
elif val == "proto_error":
|
||||
return StringError(err.localized)
|
||||
else:
|
||||
print("unhandled error type:", val)
|
||||
return StringError(err.localized)
|
||||
|
||||
|
||||
class AnkiError(Exception):
|
||||
def __init__(self, type, **data) -> None:
|
||||
|
|
|
@ -13,7 +13,7 @@ from zipfile import ZipFile
|
|||
|
||||
from anki import hooks
|
||||
from anki.collection import Collection
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
from anki.utils import ids2str, namedtmp, splitFields, stripHTML
|
||||
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../bazel-bin/pylib/anki/fluent_pb2.pyi
|
|
@ -7,8 +7,7 @@ from anki.importing.csvfile import TextImporter
|
|||
from anki.importing.mnemo import MnemosyneImporter
|
||||
from anki.importing.pauker import PaukerImporter
|
||||
from anki.importing.supermemo_xml import SupermemoXmlImporter # type: ignore
|
||||
from anki.lang import tr_legacyglobal
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR, tr_legacyglobal
|
||||
|
||||
Importers = (
|
||||
(tr_legacyglobal(TR.IMPORTING_TEXT_SEPARATED_BY_TABS_OR_SEMICOLONS), TextImporter),
|
||||
|
|
|
@ -9,7 +9,7 @@ from anki.collection import Collection
|
|||
from anki.consts import *
|
||||
from anki.decks import DeckManager
|
||||
from anki.importing.base import Importer
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
from anki.utils import intTime, joinFields, splitFields
|
||||
|
||||
GUID = 1
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import Any, List, Optional, TextIO, Union
|
|||
|
||||
from anki.collection import Collection
|
||||
from anki.importing.noteimp import ForeignNote, NoteImporter
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
|
||||
|
||||
class TextImporter(NoteImporter):
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import cast
|
|||
|
||||
from anki.db import DB
|
||||
from anki.importing.noteimp import ForeignCard, ForeignNote, NoteImporter
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
from anki.stdmodels import addBasicModel, addClozeModel
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Tuple, Union
|
|||
from anki.collection import Collection
|
||||
from anki.consts import NEW_CARDS_RANDOM, STARTING_FACTOR
|
||||
from anki.importing.base import Importer
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
from anki.utils import (
|
||||
fieldChecksum,
|
||||
guid64,
|
||||
|
|
|
@ -5,9 +5,20 @@ from __future__ import annotations
|
|||
|
||||
import locale
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Optional, Tuple
|
||||
|
||||
import anki
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
import anki._backend.fluent_pb2 as _fluent_pb
|
||||
|
||||
# public exports
|
||||
TR = _fluent_pb.FluentString
|
||||
FormatTimeSpanContext = _pb.FormatTimespanIn.Context # pylint: disable=no-member
|
||||
|
||||
# pylint: disable=no-member
|
||||
if TYPE_CHECKING:
|
||||
TRValue = _fluent_pb.FluentStringValue
|
||||
FormatTimeSpanContextValue = _pb.FormatTimespanIn.ContextValue
|
||||
|
||||
langs = sorted(
|
||||
[
|
||||
|
@ -142,7 +153,7 @@ def lang_to_disk_lang(lang: str) -> str:
|
|||
currentLang = "en"
|
||||
|
||||
# the current Fluent translation instance
|
||||
current_i18n: Optional[anki.rsbackend.RustBackend] = None
|
||||
current_i18n: Optional[anki._backend.RustBackend] = None
|
||||
|
||||
# path to locale folder
|
||||
locale_folder = ""
|
||||
|
@ -169,7 +180,7 @@ def tr_legacyglobal(*args, **kwargs) -> str:
|
|||
def set_lang(lang: str, locale_dir: str) -> None:
|
||||
global currentLang, current_i18n, locale_folder
|
||||
currentLang = lang
|
||||
current_i18n = anki.rsbackend.RustBackend(ftl_folder=locale_folder, langs=[lang])
|
||||
current_i18n = anki._backend.RustBackend(ftl_folder=locale_folder, langs=[lang])
|
||||
locale_folder = locale_dir
|
||||
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ from dataclasses import dataclass
|
|||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
import anki
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki import hooks
|
||||
from anki.lang import TR
|
||||
from anki.models import NoteType
|
||||
from anki.rsbackend import TR, pb
|
||||
from anki.template import TemplateRenderContext, TemplateRenderOutput
|
||||
from anki.utils import call, isMac, namedtmp, tmpdir
|
||||
|
||||
|
@ -45,7 +46,7 @@ class ExtractedLatexOutput:
|
|||
latex: List[ExtractedLatex]
|
||||
|
||||
@staticmethod
|
||||
def from_proto(proto: pb.ExtractLatexOut) -> ExtractedLatexOutput:
|
||||
def from_proto(proto: _pb.ExtractLatexOut) -> ExtractedLatexOutput:
|
||||
return ExtractedLatexOutput(
|
||||
html=proto.text,
|
||||
latex=[
|
||||
|
|
|
@ -14,9 +14,9 @@ import urllib.request
|
|||
from typing import Any, Callable, List, Optional, Tuple
|
||||
|
||||
import anki
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki.consts import *
|
||||
from anki.latex import render_latex, render_latex_returning_errors
|
||||
from anki.rsbackend import pb
|
||||
from anki.utils import intTime
|
||||
|
||||
|
||||
|
@ -26,6 +26,9 @@ def media_paths_from_col_path(col_path: str) -> Tuple[str, str]:
|
|||
return (media_folder, media_db)
|
||||
|
||||
|
||||
CheckMediaOut = _pb.CheckMediaOut
|
||||
|
||||
|
||||
# fixme: look into whether we can drop chdir() below
|
||||
# - need to check aa89d06304fecd3597da4565330a3e55bdbb91fe
|
||||
# - and audio handling code
|
||||
|
@ -188,7 +191,7 @@ class MediaManager:
|
|||
# Checking media
|
||||
##########################################################################
|
||||
|
||||
def check(self) -> pb.CheckMediaOut:
|
||||
def check(self) -> CheckMediaOut:
|
||||
output = self.col.backend.check_media()
|
||||
# files may have been renamed on disk, so an undo at this point could
|
||||
# break file references
|
||||
|
|
|
@ -9,17 +9,24 @@ import time
|
|||
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.backend_pb2 as pb
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki.consts import *
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.rsbackend import (
|
||||
TR,
|
||||
NotFoundError,
|
||||
StockNoteType,
|
||||
from anki.errors import NotFoundError
|
||||
from anki.lang import TR, without_unicode_isolation
|
||||
from anki.utils import (
|
||||
checksum,
|
||||
from_json_bytes,
|
||||
ids2str,
|
||||
intTime,
|
||||
joinFields,
|
||||
splitFields,
|
||||
to_json_bytes,
|
||||
)
|
||||
from anki.utils import checksum, ids2str, intTime, joinFields, splitFields
|
||||
|
||||
# public exports
|
||||
NoteTypeNameID = _pb.NoteTypeNameID
|
||||
NoteTypeNameIDUseCount = _pb.NoteTypeNameIDUseCount
|
||||
|
||||
|
||||
# types
|
||||
NoteType = Dict[str, Any]
|
||||
|
@ -121,10 +128,10 @@ class ModelManager:
|
|||
# Listing note types
|
||||
#############################################################
|
||||
|
||||
def all_names_and_ids(self) -> Sequence[pb.NoteTypeNameID]:
|
||||
def all_names_and_ids(self) -> Sequence[NoteTypeNameID]:
|
||||
return self.col.backend.get_notetype_names()
|
||||
|
||||
def all_use_counts(self) -> Sequence[pb.NoteTypeNameIDUseCount]:
|
||||
def all_use_counts(self) -> Sequence[NoteTypeNameIDUseCount]:
|
||||
return self.col.backend.get_notetype_names_and_counts()
|
||||
|
||||
# legacy
|
||||
|
@ -200,7 +207,7 @@ class ModelManager:
|
|||
# caller should call save() after modifying
|
||||
nt = from_json_bytes(
|
||||
self.col.backend.get_stock_notetype_legacy(
|
||||
StockNoteType.STOCK_NOTE_TYPE_BASIC
|
||||
_pb.StockNoteType.STOCK_NOTE_TYPE_BASIC
|
||||
)
|
||||
)
|
||||
nt["flds"] = []
|
||||
|
@ -293,7 +300,7 @@ class ModelManager:
|
|||
assert isinstance(name, str)
|
||||
nt = from_json_bytes(
|
||||
self.col.backend.get_stock_notetype_legacy(
|
||||
StockNoteType.STOCK_NOTE_TYPE_BASIC
|
||||
_pb.StockNoteType.STOCK_NOTE_TYPE_BASIC
|
||||
)
|
||||
)
|
||||
field = nt["flds"][0]
|
||||
|
@ -354,7 +361,7 @@ class ModelManager:
|
|||
def new_template(self, name: str) -> Template:
|
||||
nt = from_json_bytes(
|
||||
self.col.backend.get_stock_notetype_legacy(
|
||||
StockNoteType.STOCK_NOTE_TYPE_BASIC
|
||||
_pb.StockNoteType.STOCK_NOTE_TYPE_BASIC
|
||||
)
|
||||
)
|
||||
template = nt["tmpls"][0]
|
||||
|
@ -508,5 +515,5 @@ and notes.mid = ? and cards.ord = ?""",
|
|||
self, m: NoteType, flds: str, allowEmpty: bool = True
|
||||
) -> List[int]:
|
||||
print("_availClozeOrds() is deprecated; use note.cloze_numbers_in_fields()")
|
||||
note = anki.rsbackend.BackendNote(fields=[flds])
|
||||
note = anki._backend.BackendNote(fields=[flds])
|
||||
return list(self.col.backend.cloze_numbers_in_note(note))
|
||||
|
|
|
@ -7,9 +7,9 @@ import pprint
|
|||
from typing import Any, List, Optional, Sequence, Tuple
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki import hooks
|
||||
from anki.models import NoteType
|
||||
from anki.rsbackend import BackendNote
|
||||
from anki.utils import joinFields
|
||||
|
||||
|
||||
|
@ -41,7 +41,7 @@ class Note:
|
|||
assert n
|
||||
self._load_from_backend_note(n)
|
||||
|
||||
def _load_from_backend_note(self, n: BackendNote) -> None:
|
||||
def _load_from_backend_note(self, n: _pb.Note) -> None:
|
||||
self.id = n.id
|
||||
self.guid = n.guid
|
||||
self.mid = n.notetype_id
|
||||
|
@ -51,9 +51,9 @@ class Note:
|
|||
self.fields = list(n.fields)
|
||||
self._fmap = self.col.models.fieldMap(self.model())
|
||||
|
||||
def to_backend_note(self) -> BackendNote:
|
||||
def to_backend_note(self) -> _pb.Note:
|
||||
hooks.note_will_flush(self)
|
||||
return BackendNote(
|
||||
return _pb.Note(
|
||||
id=self.id,
|
||||
guid=self.guid,
|
||||
notetype_id=self.mid,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../../bazel-bin/pylib/anki/rsbackend_gen.py
|
|
@ -20,30 +20,25 @@ from typing import (
|
|||
)
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.backend_pb2 as pb
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki import hooks
|
||||
from anki._backend import CountsForDeckToday, FormatTimeSpanContext, SchedTimingToday
|
||||
from anki.cards import Card
|
||||
from anki.consts import *
|
||||
from anki.decks import Deck, DeckConfig, DeckManager, QueueConfig
|
||||
from anki.decks import Deck, DeckConfig, DeckManager, DeckTreeNode, QueueConfig
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import (
|
||||
TR,
|
||||
CountsForDeckToday,
|
||||
DeckTreeNode,
|
||||
FormatTimeSpanContext,
|
||||
SchedTimingToday,
|
||||
from_json_bytes,
|
||||
)
|
||||
from anki.utils import ids2str, intTime
|
||||
from anki.utils import from_json_bytes, ids2str, intTime
|
||||
|
||||
UnburyCurrentDeckMode = pb.UnburyCardsInCurrentDeckIn.Mode # pylint:disable=no-member
|
||||
BuryOrSuspendMode = pb.BuryOrSuspendCardsIn.Mode # pylint:disable=no-member
|
||||
CongratsInfoOut = anki._backend.backend_pb2.CongratsInfoOut
|
||||
|
||||
UnburyCurrentDeckMode = _pb.UnburyCardsInCurrentDeckIn.Mode # pylint:disable=no-member
|
||||
BuryOrSuspendMode = _pb.BuryOrSuspendCardsIn.Mode # pylint:disable=no-member
|
||||
if TYPE_CHECKING:
|
||||
UnburyCurrentDeckModeValue = (
|
||||
pb.UnburyCardsInCurrentDeckIn.ModeValue # pylint:disable=no-member
|
||||
_pb.UnburyCardsInCurrentDeckIn.ModeValue # pylint:disable=no-member
|
||||
)
|
||||
BuryOrSuspendModeValue = (
|
||||
pb.BuryOrSuspendCardsIn.ModeValue # pylint:disable=no-member
|
||||
_pb.BuryOrSuspendCardsIn.ModeValue # pylint:disable=no-member
|
||||
)
|
||||
|
||||
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
|
||||
|
@ -1241,7 +1236,7 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
|
|||
# Deck finished state
|
||||
##########################################################################
|
||||
|
||||
def congratulations_info(self) -> pb.CongratsInfoOut:
|
||||
def congratulations_info(self) -> CongratsInfoOut:
|
||||
return self.col.backend.congrats_info()
|
||||
|
||||
def finishedMsg(self) -> str:
|
||||
|
|
|
@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
|||
|
||||
import anki
|
||||
from anki.consts import *
|
||||
from anki.rsbackend import TR, FormatTimeSpanContext
|
||||
from anki.lang import TR, FormatTimeSpanContext
|
||||
from anki.utils import ids2str
|
||||
|
||||
# Card stats
|
||||
|
|
|
@ -5,12 +5,15 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING, Callable, List, Tuple
|
||||
|
||||
from anki._backend import StockNoteType
|
||||
from anki.collection import Collection
|
||||
from anki.models import NoteType
|
||||
from anki.rsbackend import StockNoteType, from_json_bytes
|
||||
from anki.utils import from_json_bytes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anki.backend_pb2 import StockNoteTypeValue # pylint: disable=no-name-in-module
|
||||
from anki._backend.backend_pb2 import ( # pylint: disable=no-name-in-module
|
||||
StockNoteTypeValue,
|
||||
)
|
||||
|
||||
|
||||
# add-on authors can add ("note type name", function_like_addBasicModel)
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
#
|
||||
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
|
||||
# public exports
|
||||
SyncAuth = _pb.SyncAuth
|
||||
SyncOutput = _pb.SyncCollectionOut
|
||||
SyncStatus = _pb.SyncStatusOut
|
||||
|
||||
|
||||
# Legacy attributes some add-ons may be using
|
||||
#
|
||||
|
||||
from .httpclient import HttpClient
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ except ImportError as e:
|
|||
from flask import Response
|
||||
|
||||
from anki import Collection
|
||||
from anki.backend_pb2 import SyncServerMethodIn
|
||||
from anki._backend.backend_pb2 import SyncServerMethodIn
|
||||
|
||||
Method = SyncServerMethodIn.Method # pylint: disable=no-member
|
||||
|
||||
|
|
|
@ -16,9 +16,13 @@ import re
|
|||
from typing import Collection, List, Optional, Sequence, Tuple
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
from anki.collection import SearchTerm
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
import anki.collection
|
||||
from anki.utils import ids2str
|
||||
|
||||
# public exports
|
||||
TagTreeNode = _pb.TagTreeNode
|
||||
|
||||
|
||||
class TagManager:
|
||||
def __init__(self, col: anki.collection.Collection) -> None:
|
||||
|
@ -37,6 +41,9 @@ class TagManager:
|
|||
def allItems(self) -> List[Tuple[str, int]]:
|
||||
return [(t.name, t.usn) for t in self.col.backend.all_tags()]
|
||||
|
||||
def tree(self) -> TagTreeNode:
|
||||
return self.col.backend.tag_tree()
|
||||
|
||||
# Registering and fetching tags
|
||||
#############################################################
|
||||
|
||||
|
@ -87,7 +94,7 @@ class TagManager:
|
|||
|
||||
def rename_tag(self, old: str, new: str) -> int:
|
||||
"Rename provided tag, returning number of changed notes."
|
||||
nids = self.col.find_notes(SearchTerm(tag=old))
|
||||
nids = self.col.find_notes(anki.collection.SearchTerm(tag=old))
|
||||
if not nids:
|
||||
return 0
|
||||
escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old)
|
||||
|
|
|
@ -32,13 +32,15 @@ from dataclasses import dataclass
|
|||
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
import anki
|
||||
import anki._backend.backend_pb2 as _pb
|
||||
from anki import hooks
|
||||
from anki.cards import Card
|
||||
from anki.decks import DeckManager
|
||||
from anki.errors import TemplateError
|
||||
from anki.models import NoteType
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import pb, to_json_bytes
|
||||
from anki.sound import AVTag, SoundOrVideoTag, TTSTag
|
||||
from anki.utils import to_json_bytes
|
||||
|
||||
CARD_BLANK_HELP = (
|
||||
"https://anki.tenderapp.com/kb/card-appearance/the-front-of-this-card-is-blank"
|
||||
|
@ -61,7 +63,7 @@ class PartiallyRenderedCard:
|
|||
anodes: TemplateReplacementList
|
||||
|
||||
@classmethod
|
||||
def from_proto(cls, out: pb.RenderCardOut) -> PartiallyRenderedCard:
|
||||
def from_proto(cls, out: _pb.RenderCardOut) -> PartiallyRenderedCard:
|
||||
qnodes = cls.nodes_from_proto(out.question_nodes)
|
||||
anodes = cls.nodes_from_proto(out.answer_nodes)
|
||||
|
||||
|
@ -69,7 +71,7 @@ class PartiallyRenderedCard:
|
|||
|
||||
@staticmethod
|
||||
def nodes_from_proto(
|
||||
nodes: Sequence[pb.RenderedTemplateNode],
|
||||
nodes: Sequence[_pb.RenderedTemplateNode],
|
||||
) -> TemplateReplacementList:
|
||||
results: TemplateReplacementList = []
|
||||
for node in nodes:
|
||||
|
@ -86,7 +88,7 @@ class PartiallyRenderedCard:
|
|||
return results
|
||||
|
||||
|
||||
def av_tag_to_native(tag: pb.AVTag) -> AVTag:
|
||||
def av_tag_to_native(tag: _pb.AVTag) -> AVTag:
|
||||
val = tag.WhichOneof("value")
|
||||
if val == "sound_or_video":
|
||||
return SoundOrVideoTag(filename=tag.sound_or_video)
|
||||
|
@ -100,7 +102,7 @@ def av_tag_to_native(tag: pb.AVTag) -> AVTag:
|
|||
)
|
||||
|
||||
|
||||
def av_tags_to_native(tags: Sequence[pb.AVTag]) -> List[AVTag]:
|
||||
def av_tags_to_native(tags: Sequence[_pb.AVTag]) -> List[AVTag]:
|
||||
return list(map(av_tag_to_native, tags))
|
||||
|
||||
|
||||
|
@ -206,7 +208,7 @@ class TemplateRenderContext:
|
|||
def render(self) -> TemplateRenderOutput:
|
||||
try:
|
||||
partial = self._partially_render()
|
||||
except anki.rsbackend.TemplateError as e:
|
||||
except TemplateError as e:
|
||||
return TemplateRenderOutput(
|
||||
question_text=str(e),
|
||||
answer_text=str(e),
|
||||
|
|
|
@ -26,6 +26,17 @@ from anki.dbproxy import DBProxy
|
|||
|
||||
_tmpdir: Optional[str]
|
||||
|
||||
try:
|
||||
# pylint: disable=c-extension-no-member
|
||||
import orjson
|
||||
|
||||
to_json_bytes = orjson.dumps
|
||||
from_json_bytes = orjson.loads
|
||||
except:
|
||||
print("orjson is missing; DB operations will be slower")
|
||||
to_json_bytes = lambda obj: json.dumps(obj).encode("utf8") # type: ignore
|
||||
from_json_bytes = json.loads
|
||||
|
||||
# Time handling
|
||||
##############################################################################
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ import tempfile
|
|||
|
||||
from anki import Collection as aopen
|
||||
from anki.dbproxy import emulate_named_args
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR, without_unicode_isolation
|
||||
from anki.stdmodels import addBasicModel, get_stock_notetypes
|
||||
from anki.utils import isWin
|
||||
from tests.shared import assertException, getEmptyCol
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# coding: utf-8
|
||||
import pytest
|
||||
|
||||
from anki._backend import BuiltinSortKind
|
||||
from anki.collection import ConfigBoolKey
|
||||
from anki.consts import *
|
||||
from anki.rsbackend import BuiltinSortKind
|
||||
from tests.shared import getEmptyCol, isNearCutoff
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import time
|
||||
|
||||
from anki.consts import MODEL_CLOZE
|
||||
from anki.rsbackend import NotFoundError
|
||||
from anki.errors import NotFoundError
|
||||
from anki.utils import isWin, stripHTML
|
||||
from tests.shared import getEmptyCol
|
||||
|
||||
|
|
|
@ -22,20 +22,6 @@ py_binary(
|
|||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "genbackend",
|
||||
srcs = [
|
||||
"genbackend.py",
|
||||
"//pylib/anki:backend_pb2",
|
||||
],
|
||||
visibility = ["//pylib:__subpackages__"],
|
||||
deps = [
|
||||
requirement("black"),
|
||||
requirement("stringcase"),
|
||||
requirement("protobuf"),
|
||||
],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = "hookslib",
|
||||
srcs = ["hookslib.py"],
|
||||
|
|
|
@ -14,8 +14,8 @@ from typing import Any, Callable, Dict, Optional, Union
|
|||
|
||||
import anki.lang
|
||||
from anki import version as _version
|
||||
from anki._backend import RustBackend
|
||||
from anki.consts import HELP_SITE
|
||||
from anki.rsbackend import RustBackend
|
||||
from anki.utils import checksum, isLin, isMac
|
||||
from aqt.qt import *
|
||||
from aqt.utils import TR, locale_dir, tr
|
||||
|
|
|
@ -13,8 +13,9 @@ from typing import List, Optional, Sequence, Tuple, cast
|
|||
import aqt
|
||||
import aqt.forms
|
||||
from anki.cards import Card
|
||||
from anki.collection import Collection, ConfigBoolKey, InvalidInput, SearchTerm
|
||||
from anki.collection import Collection, ConfigBoolKey, SearchTerm
|
||||
from anki.consts import *
|
||||
from anki.errors import InvalidInput
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.models import NoteType
|
||||
from anki.notes import Note
|
||||
|
|
|
@ -9,9 +9,9 @@ from typing import Any, Dict, List, Optional
|
|||
import aqt
|
||||
from anki.cards import Card
|
||||
from anki.consts import *
|
||||
from anki.errors import TemplateError
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import TemplateError
|
||||
from anki.template import TemplateRenderContext
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import aqt
|
||||
from anki.rsbackend import DatabaseCheckProgress, ProgressKind
|
||||
from anki.collection import DatabaseCheckProgress, ProgressKind
|
||||
from aqt.qt import *
|
||||
from aqt.utils import showText, tooltip
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ from dataclasses import dataclass
|
|||
from typing import Any
|
||||
|
||||
import aqt
|
||||
from anki.decks import DeckTreeNode
|
||||
from anki.errors import DeckRenameError
|
||||
from anki.rsbackend import DeckTreeNode
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.sound import av_player
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
from typing import List, Optional
|
||||
|
||||
import aqt
|
||||
from anki.collection import InvalidInput, SearchTerm
|
||||
from anki.collection import SearchTerm
|
||||
from anki.errors import InvalidInput
|
||||
from anki.lang import without_unicode_isolation
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
import re
|
||||
|
||||
import aqt
|
||||
from anki.backend_pb2 import EmptyCardsReport, NoteWithEmptyCards
|
||||
from anki.collection import EmptyCardsReport, NoteWithEmptyCards
|
||||
from aqt import gui_hooks
|
||||
from aqt.qt import QDialog, QDialogButtonBox, qconnect
|
||||
from aqt.utils import TR, disable_help_button, restoreGeom, saveGeom, tooltip, tr
|
||||
|
@ -24,7 +24,7 @@ def show_empty_cards(mw: aqt.main.AnkiQt) -> None:
|
|||
diag = EmptyCardsDialog(mw, report)
|
||||
diag.show()
|
||||
|
||||
mw.taskman.run_in_background(mw.col.backend.get_empty_cards, on_done)
|
||||
mw.taskman.run_in_background(mw.col.get_empty_cards, on_done)
|
||||
|
||||
|
||||
class EmptyCardsDialog(QDialog):
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
import aqt
|
||||
from anki.consts import *
|
||||
from anki.errors import TemplateError
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.models import NoteType
|
||||
from anki.rsbackend import TemplateError
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.schema_change_tracker import ChangeTracker
|
||||
|
|
|
@ -26,11 +26,11 @@ import aqt.stats
|
|||
import aqt.toolbar
|
||||
import aqt.webview
|
||||
from anki import hooks
|
||||
from anki._backend import RustBackend as _RustBackend
|
||||
from anki.collection import Collection, SearchTerm
|
||||
from anki.decks import Deck
|
||||
from anki.hooks import runHook
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.rsbackend import RustBackend
|
||||
from anki.sound import AVTag, SoundOrVideoTag
|
||||
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
|
||||
from aqt import gui_hooks
|
||||
|
@ -100,7 +100,7 @@ class AnkiQt(QMainWindow):
|
|||
self,
|
||||
app: QApplication,
|
||||
profileManager: ProfileManagerType,
|
||||
backend: RustBackend,
|
||||
backend: _RustBackend,
|
||||
opts: Namespace,
|
||||
args: List[Any],
|
||||
) -> None:
|
||||
|
|
|
@ -9,8 +9,10 @@ from concurrent.futures import Future
|
|||
from typing import Iterable, List, Optional, Sequence, TypeVar
|
||||
|
||||
import aqt
|
||||
from anki.collection import SearchTerm
|
||||
from anki.rsbackend import TR, Interrupted, ProgressKind, pb
|
||||
from anki.collection import ProgressKind, SearchTerm
|
||||
from anki.errors import Interrupted
|
||||
from anki.lang import TR
|
||||
from anki.media import CheckMediaOut
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
askUser,
|
||||
|
@ -74,7 +76,7 @@ class MediaChecker:
|
|||
|
||||
self.mw.taskman.run_on_main(lambda: self.mw.progress.update(progress.val))
|
||||
|
||||
def _check(self) -> pb.CheckMediaOut:
|
||||
def _check(self) -> CheckMediaOut:
|
||||
"Run the check on a background thread."
|
||||
return self.mw.col.media.check()
|
||||
|
||||
|
@ -87,7 +89,7 @@ class MediaChecker:
|
|||
if isinstance(exc, Interrupted):
|
||||
return
|
||||
|
||||
output: pb.CheckMediaOut = future.result()
|
||||
output: CheckMediaOut = future.result()
|
||||
report = output.report
|
||||
|
||||
# show report and offer to delete
|
||||
|
|
|
@ -18,11 +18,10 @@ import flask_cors # type: ignore
|
|||
from flask import Response, request
|
||||
from waitress.server import create_server
|
||||
|
||||
import anki.backend_pb2 as pb
|
||||
import aqt
|
||||
from anki import hooks
|
||||
from anki.rsbackend import from_json_bytes
|
||||
from anki.utils import devMode
|
||||
from anki.collection import GraphPreferences
|
||||
from anki.utils import devMode, from_json_bytes
|
||||
from aqt.qt import *
|
||||
from aqt.utils import aqt_data_folder
|
||||
|
||||
|
@ -253,22 +252,21 @@ def _redirectWebExports(path):
|
|||
|
||||
def graph_data() -> bytes:
|
||||
args = from_json_bytes(request.data)
|
||||
return aqt.mw.col.backend.graphs(search=args["search"], days=args["days"])
|
||||
return aqt.mw.col.graph_data(search=args["search"], days=args["days"])
|
||||
|
||||
|
||||
def graph_preferences() -> bytes:
|
||||
return aqt.mw.col.backend.get_graph_preferences()
|
||||
return aqt.mw.col.get_graph_preferences()
|
||||
|
||||
|
||||
def set_graph_preferences() -> None:
|
||||
input = pb.GraphPreferences()
|
||||
input.ParseFromString(request.data)
|
||||
aqt.mw.col.backend.set_graph_preferences(input=input)
|
||||
prefs = GraphPreferences()
|
||||
prefs.ParseFromString(request.data)
|
||||
aqt.mw.col.set_graph_preferences(prefs)
|
||||
|
||||
|
||||
def congrats_info() -> bytes:
|
||||
info = aqt.mw.col.backend.congrats_info()
|
||||
return info.SerializeToString()
|
||||
return aqt.mw.col.congrats_info()
|
||||
|
||||
|
||||
post_handlers = {
|
||||
|
|
|
@ -9,13 +9,9 @@ from dataclasses import dataclass
|
|||
from typing import Callable, List, Optional, Union
|
||||
|
||||
import aqt
|
||||
from anki.rsbackend import (
|
||||
TR,
|
||||
Interrupted,
|
||||
MediaSyncProgress,
|
||||
NetworkError,
|
||||
ProgressKind,
|
||||
)
|
||||
from anki.collection import MediaSyncProgress, ProgressKind
|
||||
from anki.errors import Interrupted, NetworkError
|
||||
from anki.lang import TR
|
||||
from anki.types import assert_exhaustive
|
||||
from anki.utils import intTime
|
||||
from aqt import gui_hooks
|
||||
|
|
|
@ -6,11 +6,9 @@ from typing import Any, List, Optional, Sequence
|
|||
|
||||
import aqt.clayout
|
||||
from anki import stdmodels
|
||||
from anki.backend_pb2 import NoteTypeNameIDUseCount
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.models import NoteType
|
||||
from anki.models import NoteType, NoteTypeNameIDUseCount
|
||||
from anki.notes import Note
|
||||
from anki.rsbackend import pb
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
|
@ -51,7 +49,7 @@ class Models(QDialog):
|
|||
self.form.buttonBox.helpRequested,
|
||||
lambda: openHelp(HelpPage.ADDING_A_NOTE_TYPE),
|
||||
)
|
||||
self.models: List[pb.NoteTypeNameIDUseCount] = []
|
||||
self.models: List[NoteTypeNameIDUseCount] = []
|
||||
self.setupModels()
|
||||
restoreGeom(self, "models")
|
||||
self.exec_()
|
||||
|
@ -111,7 +109,7 @@ class Models(QDialog):
|
|||
self.saveAndRefresh(nt)
|
||||
|
||||
def saveAndRefresh(self, nt: NoteType) -> None:
|
||||
def save() -> Sequence[pb.NoteTypeNameIDUseCount]:
|
||||
def save() -> Sequence[NoteTypeNameIDUseCount]:
|
||||
self.mm.save(nt)
|
||||
return self.col.models.all_use_counts()
|
||||
|
||||
|
@ -161,7 +159,7 @@ class Models(QDialog):
|
|||
|
||||
nt = self.current_notetype()
|
||||
|
||||
def save() -> Sequence[pb.NoteTypeNameIDUseCount]:
|
||||
def save() -> Sequence[NoteTypeNameIDUseCount]:
|
||||
self.mm.rem(nt)
|
||||
return self.col.models.all_use_counts()
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import aqt.sound
|
|||
from anki import Collection
|
||||
from anki.db import DB
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.rsbackend import SyncAuth
|
||||
from anki.sync import SyncAuth
|
||||
from anki.utils import intTime, isMac, isWin
|
||||
from aqt import appHelpSite
|
||||
from aqt.qt import *
|
||||
|
|
|
@ -9,9 +9,10 @@ from enum import Enum
|
|||
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Sequence, Tuple, cast
|
||||
|
||||
import aqt
|
||||
from anki.collection import ConfigBoolKey, InvalidInput, SearchTerm
|
||||
from anki.errors import DeckRenameError
|
||||
from anki.rsbackend import DeckTreeNode, TagTreeNode
|
||||
from anki.collection import ConfigBoolKey, SearchTerm
|
||||
from anki.decks import DeckTreeNode
|
||||
from anki.errors import DeckRenameError, InvalidInput
|
||||
from anki.tags import TagTreeNode
|
||||
from aqt import gui_hooks
|
||||
from aqt.main import ResetReason
|
||||
from aqt.models import Models
|
||||
|
@ -524,7 +525,7 @@ class SidebarTreeView(QTreeView):
|
|||
newhead = head + node.name + "::"
|
||||
render(item, node.children, newhead)
|
||||
|
||||
tree = self.col.backend.tag_tree()
|
||||
tree = self.col.tags.tree()
|
||||
root = self._section_root(
|
||||
root=root,
|
||||
name=TR.BROWSING_SIDEBAR_TAGS,
|
||||
|
|
|
@ -8,18 +8,10 @@ import os
|
|||
from typing import Callable, Tuple
|
||||
|
||||
import aqt
|
||||
from anki.lang import without_unicode_isolation
|
||||
from anki.rsbackend import (
|
||||
TR,
|
||||
FullSyncProgress,
|
||||
Interrupted,
|
||||
NormalSyncProgress,
|
||||
ProgressKind,
|
||||
SyncError,
|
||||
SyncErrorKind,
|
||||
SyncOutput,
|
||||
SyncStatus,
|
||||
)
|
||||
from anki.collection import FullSyncProgress, NormalSyncProgress, ProgressKind
|
||||
from anki.errors import Interrupted, SyncError
|
||||
from anki.lang import TR, without_unicode_isolation
|
||||
from anki.sync import SyncOutput, SyncStatus
|
||||
from anki.utils import platDesc
|
||||
from aqt.qt import (
|
||||
QDialog,
|
||||
|
@ -69,7 +61,7 @@ def get_sync_status(mw: aqt.main.AnkiQt, callback: Callable[[SyncStatus], None])
|
|||
|
||||
def handle_sync_error(mw: aqt.main.AnkiQt, err: Exception):
|
||||
if isinstance(err, SyncError):
|
||||
if err.kind() == SyncErrorKind.AUTH_FAILED:
|
||||
if err.is_auth_error():
|
||||
mw.pm.clear_sync_auth()
|
||||
elif isinstance(err, Interrupted):
|
||||
# no message to show
|
||||
|
@ -249,7 +241,7 @@ def sync_login(
|
|||
try:
|
||||
auth = fut.result()
|
||||
except SyncError as e:
|
||||
if e.kind() == SyncErrorKind.AUTH_FAILED:
|
||||
if e.is_auth_error():
|
||||
showWarning(str(e))
|
||||
sync_login(mw, on_success, username, password)
|
||||
else:
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
from typing import Any, Dict, Optional
|
||||
|
||||
import aqt
|
||||
from anki.rsbackend import SyncStatus
|
||||
from anki.sync import SyncStatus
|
||||
from aqt import gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.sync import get_sync_status
|
||||
|
|
|
@ -14,13 +14,14 @@ from markdown import markdown
|
|||
|
||||
import anki
|
||||
import aqt
|
||||
from anki.rsbackend import TR, InvalidInput # pylint: disable=unused-import
|
||||
from anki.errors import InvalidInput
|
||||
from anki.lang import TR # pylint: disable=unused-import
|
||||
from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild
|
||||
from aqt.qt import *
|
||||
from aqt.theme import theme_manager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from anki.rsbackend import TRValue
|
||||
from anki.lang import TRValue
|
||||
|
||||
TextFormat = Union[Literal["plain", "rich"]]
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import anki.lang
|
||||
from anki.rsbackend import TR
|
||||
from anki.lang import TR
|
||||
|
||||
|
||||
def test_no_collection_i18n():
|
||||
|
|
Loading…
Reference in New Issue