bridge->backend

This commit is contained in:
Damien Elmes 2019-12-25 08:59:33 +10:00
parent 5a8d088531
commit 3e1b474dca
17 changed files with 72 additions and 70 deletions

4
.gitignore vendored
View file

@ -10,10 +10,10 @@
.pytype .pytype
__pycache__ __pycache__
anki/buildhash.py anki/buildhash.py
anki/bridge_pb2.* anki/backend_pb2.*
aqt/forms aqt/forms
locale locale
rs/ankirs/src/proto.rs rs/ankirs/src/backend_proto.rs
rs/target rs/target
tools/runanki.system tools/runanki.system
ts/node_modules ts/node_modules

View file

@ -1,5 +1,5 @@
[settings] [settings]
skip=aqt/forms,anki/bridge_pb2.py,bridge_pb2.pyi skip=aqt/forms,anki/backend_pb2.py,backend_pb2.pyi
multi_line_output=3 multi_line_output=3
include_trailing_comma=True include_trailing_comma=True
force_grid_wrap=0 force_grid_wrap=0

View file

@ -126,11 +126,11 @@ BUILDDEPS := .build/ui .build/js .build/rs .build/py-proto
@touch $@ @touch $@
.build/rs: .build/rust-deps $(RUNREQS) $(RSDEPS) $(PROTODEPS) .build/rs: .build/rust-deps $(RUNREQS) $(RSDEPS) $(PROTODEPS)
(cd rs/pybridge && maturin develop $(RUSTARGS)) (cd rs/pymod && maturin develop $(RUSTARGS))
@touch $@ @touch $@
.build/py-proto: $(RUNREQS) $(PROTODEPS) .build/py-proto: $(RUNREQS) $(PROTODEPS)
protoc --proto_path=proto --python_out=anki --mypy_out=anki proto/bridge.proto protoc --proto_path=proto --python_out=anki --mypy_out=anki proto/backend.proto
@touch $@ @touch $@
.PHONY: build clean .PHONY: build clean

View file

@ -6,9 +6,6 @@ import sys
from anki.storage import Collection from anki.storage import Collection
# temporary
from . import rsbridge
if sys.version_info[0] < 3 or sys.version_info[1] < 5: if sys.version_info[0] < 3 or sys.version_info[1] < 5:
raise Exception("Anki requires Python 3.5+") raise Exception("Anki requires Python 3.5+")

View file

@ -4,14 +4,14 @@ from typing import Dict, List
import _ankirs # pytype: disable=import-error import _ankirs # pytype: disable=import-error
import anki.bridge_pb2 as pb import anki.backend_pb2 as pb
from .types import AllTemplateReqs from .types import AllTemplateReqs
class BridgeException(Exception): class BackendException(Exception):
def __str__(self) -> str: def __str__(self) -> str:
err: pb.BridgeError = self.args[0] # pylint: disable=unsubscriptable-object err: pb.BackendError = self.args[0] # pylint: disable=unsubscriptable-object
kind = err.WhichOneof("value") kind = err.WhichOneof("value")
if kind == "invalid_input": if kind == "invalid_input":
return f"invalid input: {err.invalid_input.info}" return f"invalid input: {err.invalid_input.info}"
@ -39,30 +39,30 @@ def proto_template_reqs_to_legacy(
return legacy_reqs return legacy_reqs
class RSBridge: class Backend:
def __init__(self): def __init__(self):
self._bridge = _ankirs.Bridge() self._backend = _ankirs.Backend()
def _run_command(self, input: pb.BridgeInput) -> pb.BridgeOutput: def _run_command(self, input: pb.BackendInput) -> pb.BackendOutput:
input_bytes = input.SerializeToString() input_bytes = input.SerializeToString()
output_bytes = self._bridge.command(input_bytes) output_bytes = self._backend.command(input_bytes)
output = pb.BridgeOutput() output = pb.BackendOutput()
output.ParseFromString(output_bytes) output.ParseFromString(output_bytes)
kind = output.WhichOneof("value") kind = output.WhichOneof("value")
if kind == "error": if kind == "error":
raise BridgeException(output.error) raise BackendException(output.error)
else: else:
return output return output
def plus_one(self, num: int) -> int: def plus_one(self, num: int) -> int:
input = pb.BridgeInput(plus_one=pb.PlusOneIn(num=num)) input = pb.BackendInput(plus_one=pb.PlusOneIn(num=num))
output = self._run_command(input) output = self._run_command(input)
return output.plus_one.num return output.plus_one.num
def template_requirements( def template_requirements(
self, template_fronts: List[str], field_map: Dict[str, int] self, template_fronts: List[str], field_map: Dict[str, int]
) -> AllTemplateReqs: ) -> AllTemplateReqs:
input = pb.BridgeInput( input = pb.BackendInput(
template_requirements=pb.TemplateRequirementsIn( template_requirements=pb.TemplateRequirementsIn(
template_front=template_fronts, field_names_to_ordinals=field_map template_front=template_fronts, field_names_to_ordinals=field_map
) )

View file

@ -19,6 +19,7 @@ import anki.find
import anki.latex # sets up hook import anki.latex # sets up hook
import anki.notes import anki.notes
import anki.template import anki.template
from anki.backend import Backend
from anki.cards import Card from anki.cards import Card
from anki.consts import * from anki.consts import *
from anki.db import DB from anki.db import DB
@ -29,7 +30,6 @@ from anki.lang import _, ngettext
from anki.media import MediaManager from anki.media import MediaManager
from anki.models import ModelManager from anki.models import ModelManager
from anki.notes import Note from anki.notes import Note
from anki.rsbridge import RSBridge
from anki.sched import Scheduler as V1Scheduler from anki.sched import Scheduler as V1Scheduler
from anki.schedv2 import Scheduler as V2Scheduler from anki.schedv2 import Scheduler as V2Scheduler
from anki.sound import stripSounds from anki.sound import stripSounds
@ -85,12 +85,12 @@ class _Collection:
ls: int ls: int
conf: Dict[str, Any] conf: Dict[str, Any]
_undo: List[Any] _undo: List[Any]
rust: RSBridge backend: Backend
def __init__( def __init__(
self, db: DB, server: bool = False, log: bool = False, rust: RSBridge = None self, db: DB, backend: Backend, server: bool = False, log: bool = False
) -> None: ) -> None:
self.rust = rust self.backend = backend
self._debugLog = log self._debugLog = log
self.db = db self.db = db
self.path = db._path self.path = db._path

View file

@ -574,7 +574,7 @@ select id from notes where mid = ?)"""
field_map = {} field_map = {}
for (idx, fld) in enumerate(m["flds"]): for (idx, fld) in enumerate(m["flds"]):
field_map[fld["name"]] = idx field_map[fld["name"]] = idx
reqs = self.col.rust.template_requirements(fronts, field_map) reqs = self.col.backend.template_requirements(fronts, field_map)
m["req"] = [list(l) for l in reqs] m["req"] = [list(l) for l in reqs]
def _reqForTemplate( def _reqForTemplate(

View file

@ -8,11 +8,11 @@ import os
import re import re
from typing import Any, Dict, Tuple from typing import Any, Dict, Tuple
from anki.backend import Backend
from anki.collection import _Collection from anki.collection import _Collection
from anki.consts import * from anki.consts import *
from anki.db import DB from anki.db import DB
from anki.lang import _ from anki.lang import _
from anki.rsbridge import RSBridge
from anki.stdmodels import ( from anki.stdmodels import (
addBasicModel, addBasicModel,
addBasicTypingModel, addBasicTypingModel,
@ -27,8 +27,10 @@ def Collection(
path: str, lock: bool = True, server: bool = False, log: bool = False path: str, lock: bool = True, server: bool = False, log: bool = False
) -> _Collection: ) -> _Collection:
"Open a new or existing collection. Path must be unicode." "Open a new or existing collection. Path must be unicode."
bridge = RSBridge() backend = Backend()
assert bridge.plus_one(5) == 6 # fixme: this call is temporarily here to ensure the brige is working
# on all platforms, and should be removed in a future beta
assert backend.plus_one(5) == 6
assert path.endswith(".anki2") assert path.endswith(".anki2")
path = os.path.abspath(path) path = os.path.abspath(path)
create = not os.path.exists(path) create = not os.path.exists(path)
@ -49,7 +51,7 @@ def Collection(
db.execute("pragma journal_mode = wal") db.execute("pragma journal_mode = wal")
db.setAutocommit(False) db.setAutocommit(False)
# add db to col and do any remaining upgrades # add db to col and do any remaining upgrades
col = _Collection(db, server, log, rust=bridge) col = _Collection(db, backend=backend, server=server, log=log)
if ver < SCHEMA_VERSION: if ver < SCHEMA_VERSION:
_upgrade(col, ver) _upgrade(col, ver)
elif ver > SCHEMA_VERSION: elif ver > SCHEMA_VERSION:

View file

@ -1,25 +1,25 @@
syntax = "proto3"; syntax = "proto3";
package proto; package backend_proto;
message Empty {} message Empty {}
message BridgeInput { message BackendInput {
oneof value { oneof value {
PlusOneIn plus_one = 2; PlusOneIn plus_one = 2;
TemplateRequirementsIn template_requirements = 3; TemplateRequirementsIn template_requirements = 3;
} }
} }
message BridgeOutput { message BackendOutput {
oneof value { oneof value {
BridgeError error = 1; BackendError error = 1;
PlusOneOut plus_one = 2; PlusOneOut plus_one = 2;
TemplateRequirementsOut template_requirements = 3; TemplateRequirementsOut template_requirements = 3;
} }
} }
message BridgeError { message BackendError {
oneof value { oneof value {
InvalidInputError invalid_input = 1; InvalidInputError invalid_input = 1;
TemplateParseError template_parse = 2; TemplateParseError template_parse = 2;

2
rs/Cargo.lock generated
View file

@ -429,7 +429,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "pybridge" name = "pymod"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ankirs", "ankirs",

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["ankirs", "pybridge"] members = ["ankirs", "pymod"]
[profile.release] [profile.release]
lto = true lto = true

View file

@ -3,5 +3,5 @@ use prost_build;
fn main() { fn main() {
// avoid default OUT_DIR for now, for code completion // avoid default OUT_DIR for now, for code completion
std::env::set_var("OUT_DIR", "src"); std::env::set_var("OUT_DIR", "src");
prost_build::compile_protos(&["../../proto/bridge.proto"], &["../../proto/"]).unwrap(); prost_build::compile_protos(&["../../proto/backend.proto"], &["../../proto/"]).unwrap();
} }

View file

@ -1,22 +1,22 @@
use crate::backend_proto as pt;
use crate::backend_proto::backend_input::Value;
use crate::err::{AnkiError, Result}; use crate::err::{AnkiError, Result};
use crate::proto as pt;
use crate::proto::bridge_input::Value;
use crate::template::{FieldMap, FieldRequirements, ParsedTemplate}; use crate::template::{FieldMap, FieldRequirements, ParsedTemplate};
use prost::Message; use prost::Message;
use std::collections::HashSet; use std::collections::HashSet;
pub struct Bridge {} pub struct Backend {}
impl Default for Bridge { impl Default for Backend {
fn default() -> Self { fn default() -> Self {
Bridge {} Backend {}
} }
} }
/// Convert an Anki error to a protobuf error. /// Convert an Anki error to a protobuf error.
impl std::convert::From<AnkiError> for pt::BridgeError { impl std::convert::From<AnkiError> for pt::BackendError {
fn from(err: AnkiError) -> Self { fn from(err: AnkiError) -> Self {
use pt::bridge_error::Value as V; use pt::backend_error::Value as V;
let value = match err { let value = match err {
AnkiError::InvalidInput { info } => V::InvalidInput(pt::InvalidInputError { info }), AnkiError::InvalidInput { info } => V::InvalidInput(pt::InvalidInputError { info }),
AnkiError::TemplateParseError { info } => { AnkiError::TemplateParseError { info } => {
@ -24,32 +24,32 @@ impl std::convert::From<AnkiError> for pt::BridgeError {
} }
}; };
pt::BridgeError { value: Some(value) } pt::BackendError { value: Some(value) }
} }
} }
// Convert an Anki error to a protobuf output. // Convert an Anki error to a protobuf output.
impl std::convert::From<AnkiError> for pt::bridge_output::Value { impl std::convert::From<AnkiError> for pt::backend_output::Value {
fn from(err: AnkiError) -> Self { fn from(err: AnkiError) -> Self {
pt::bridge_output::Value::Error(err.into()) pt::backend_output::Value::Error(err.into())
} }
} }
impl Bridge { impl Backend {
pub fn new() -> Bridge { pub fn new() -> Backend {
Bridge::default() Backend::default()
} }
/// Decode a request, process it, and return the encoded result. /// Decode a request, process it, and return the encoded result.
pub fn run_command_bytes(&mut self, req: &[u8]) -> Vec<u8> { pub fn run_command_bytes(&mut self, req: &[u8]) -> Vec<u8> {
let mut buf = vec![]; let mut buf = vec![];
let req = match pt::BridgeInput::decode(req) { let req = match pt::BackendInput::decode(req) {
Ok(req) => req, Ok(req) => req,
Err(_e) => { Err(_e) => {
// unable to decode // unable to decode
let err = AnkiError::invalid_input("couldn't decode bridge request"); let err = AnkiError::invalid_input("couldn't decode backend request");
let output = pt::BridgeOutput { let output = pt::BackendOutput {
value: Some(err.into()), value: Some(err.into()),
}; };
output.encode(&mut buf).expect("encode failed"); output.encode(&mut buf).expect("encode failed");
@ -62,21 +62,24 @@ impl Bridge {
buf buf
} }
fn run_command(&self, input: pt::BridgeInput) -> pt::BridgeOutput { fn run_command(&self, input: pt::BackendInput) -> pt::BackendOutput {
let oval = if let Some(ival) = input.value { let oval = if let Some(ival) = input.value {
match self.run_command_inner(ival) { match self.run_command_inner(ival) {
Ok(output) => output, Ok(output) => output,
Err(err) => err.into(), Err(err) => err.into(),
} }
} else { } else {
AnkiError::invalid_input("unrecognized bridge input value").into() AnkiError::invalid_input("unrecognized backend input value").into()
}; };
pt::BridgeOutput { value: Some(oval) } pt::BackendOutput { value: Some(oval) }
} }
fn run_command_inner(&self, ival: pt::bridge_input::Value) -> Result<pt::bridge_output::Value> { fn run_command_inner(
use pt::bridge_output::Value as OValue; &self,
ival: pt::backend_input::Value,
) -> Result<pt::backend_output::Value> {
use pt::backend_output::Value as OValue;
Ok(match ival { Ok(match ival {
Value::TemplateRequirements(input) => { Value::TemplateRequirements(input) => {
OValue::TemplateRequirements(self.template_requirements(input)?) OValue::TemplateRequirements(self.template_requirements(input)?)
@ -100,7 +103,7 @@ impl Bridge {
.map(|(name, ord)| (name.as_str(), *ord as u16)) .map(|(name, ord)| (name.as_str(), *ord as u16))
.collect(); .collect();
// map each provided template into a requirements list // map each provided template into a requirements list
use crate::proto::template_requirement::Value; use crate::backend_proto::template_requirement::Value;
let all_reqs = input let all_reqs = input
.template_front .template_front
.into_iter() .into_iter()

View file

@ -1,5 +1,5 @@
mod proto; mod backend_proto;
pub mod bridge; pub mod backend;
pub mod err; pub mod err;
pub mod template; pub mod template;

View file

@ -1,5 +1,5 @@
[package] [package]
name = "pybridge" name = "pymod"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2018"
authors = ["Ankitects Pty Ltd and contributors"] authors = ["Ankitects Pty Ltd and contributors"]

View file

@ -1,25 +1,25 @@
use ankirs::bridge::Bridge as RustBridge; use ankirs::backend::Backend as RustBackend;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::PyBytes; use pyo3::types::PyBytes;
#[pyclass] #[pyclass]
struct Bridge { struct Backend {
bridge: RustBridge, backend: RustBackend,
} }
#[pymethods] #[pymethods]
impl Bridge { impl Backend {
#[new] #[new]
fn init(obj: &PyRawObject) { fn init(obj: &PyRawObject) {
obj.init({ obj.init({
Bridge { Backend {
bridge: Default::default(), backend: Default::default(),
} }
}); });
} }
fn command(&mut self, py: Python, input: &PyBytes) -> PyResult<PyObject> { fn command(&mut self, py: Python, input: &PyBytes) -> PyResult<PyObject> {
let out_bytes = self.bridge.run_command_bytes(input.as_bytes()); let out_bytes = self.backend.run_command_bytes(input.as_bytes());
let out_obj = PyBytes::new(py, &out_bytes); let out_obj = PyBytes::new(py, &out_bytes);
Ok(out_obj.into()) Ok(out_obj.into())
} }
@ -27,7 +27,7 @@ impl Bridge {
#[pymodule] #[pymodule]
fn _ankirs(_py: Python, m: &PyModule) -> PyResult<()> { fn _ankirs(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Bridge>()?; m.add_class::<Backend>()?;
Ok(()) Ok(())
} }

View file

@ -1 +1 @@
ignore = ["proto.rs"] ignore = ["backend_proto.rs"]