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
__pycache__
anki/buildhash.py
anki/bridge_pb2.*
anki/backend_pb2.*
aqt/forms
locale
rs/ankirs/src/proto.rs
rs/ankirs/src/backend_proto.rs
rs/target
tools/runanki.system
ts/node_modules

View File

@ -1,5 +1,5 @@
[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
include_trailing_comma=True
force_grid_wrap=0

View File

@ -126,11 +126,11 @@ BUILDDEPS := .build/ui .build/js .build/rs .build/py-proto
@touch $@
.build/rs: .build/rust-deps $(RUNREQS) $(RSDEPS) $(PROTODEPS)
(cd rs/pybridge && maturin develop $(RUSTARGS))
(cd rs/pymod && maturin develop $(RUSTARGS))
@touch $@
.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 $@
.PHONY: build clean

View File

@ -6,9 +6,6 @@ import sys
from anki.storage import Collection
# temporary
from . import rsbridge
if sys.version_info[0] < 3 or sys.version_info[1] < 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 anki.bridge_pb2 as pb
import anki.backend_pb2 as pb
from .types import AllTemplateReqs
class BridgeException(Exception):
class BackendException(Exception):
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")
if kind == "invalid_input":
return f"invalid input: {err.invalid_input.info}"
@ -39,30 +39,30 @@ def proto_template_reqs_to_legacy(
return legacy_reqs
class RSBridge:
class Backend:
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()
output_bytes = self._bridge.command(input_bytes)
output = pb.BridgeOutput()
output_bytes = self._backend.command(input_bytes)
output = pb.BackendOutput()
output.ParseFromString(output_bytes)
kind = output.WhichOneof("value")
if kind == "error":
raise BridgeException(output.error)
raise BackendException(output.error)
else:
return output
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)
return output.plus_one.num
def template_requirements(
self, template_fronts: List[str], field_map: Dict[str, int]
) -> AllTemplateReqs:
input = pb.BridgeInput(
input = pb.BackendInput(
template_requirements=pb.TemplateRequirementsIn(
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.notes
import anki.template
from anki.backend import Backend
from anki.cards import Card
from anki.consts import *
from anki.db import DB
@ -29,7 +30,6 @@ from anki.lang import _, ngettext
from anki.media import MediaManager
from anki.models import ModelManager
from anki.notes import Note
from anki.rsbridge import RSBridge
from anki.sched import Scheduler as V1Scheduler
from anki.schedv2 import Scheduler as V2Scheduler
from anki.sound import stripSounds
@ -85,12 +85,12 @@ class _Collection:
ls: int
conf: Dict[str, Any]
_undo: List[Any]
rust: RSBridge
backend: Backend
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:
self.rust = rust
self.backend = backend
self._debugLog = log
self.db = db
self.path = db._path

View File

@ -574,7 +574,7 @@ select id from notes where mid = ?)"""
field_map = {}
for (idx, fld) in enumerate(m["flds"]):
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]
def _reqForTemplate(

View File

@ -8,11 +8,11 @@ import os
import re
from typing import Any, Dict, Tuple
from anki.backend import Backend
from anki.collection import _Collection
from anki.consts import *
from anki.db import DB
from anki.lang import _
from anki.rsbridge import RSBridge
from anki.stdmodels import (
addBasicModel,
addBasicTypingModel,
@ -27,8 +27,10 @@ def Collection(
path: str, lock: bool = True, server: bool = False, log: bool = False
) -> _Collection:
"Open a new or existing collection. Path must be unicode."
bridge = RSBridge()
assert bridge.plus_one(5) == 6
backend = Backend()
# 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")
path = os.path.abspath(path)
create = not os.path.exists(path)
@ -49,7 +51,7 @@ def Collection(
db.execute("pragma journal_mode = wal")
db.setAutocommit(False)
# 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:
_upgrade(col, ver)
elif ver > SCHEMA_VERSION:

View File

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

2
rs/Cargo.lock generated
View File

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

View File

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

View File

@ -3,5 +3,5 @@ use prost_build;
fn main() {
// avoid default OUT_DIR for now, for code completion
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::proto as pt;
use crate::proto::bridge_input::Value;
use crate::template::{FieldMap, FieldRequirements, ParsedTemplate};
use prost::Message;
use std::collections::HashSet;
pub struct Bridge {}
pub struct Backend {}
impl Default for Bridge {
impl Default for Backend {
fn default() -> Self {
Bridge {}
Backend {}
}
}
/// 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 {
use pt::bridge_error::Value as V;
use pt::backend_error::Value as V;
let value = match err {
AnkiError::InvalidInput { info } => V::InvalidInput(pt::InvalidInputError { 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.
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 {
pt::bridge_output::Value::Error(err.into())
pt::backend_output::Value::Error(err.into())
}
}
impl Bridge {
pub fn new() -> Bridge {
Bridge::default()
impl Backend {
pub fn new() -> Backend {
Backend::default()
}
/// Decode a request, process it, and return the encoded result.
pub fn run_command_bytes(&mut self, req: &[u8]) -> Vec<u8> {
let mut buf = vec![];
let req = match pt::BridgeInput::decode(req) {
let req = match pt::BackendInput::decode(req) {
Ok(req) => req,
Err(_e) => {
// unable to decode
let err = AnkiError::invalid_input("couldn't decode bridge request");
let output = pt::BridgeOutput {
let err = AnkiError::invalid_input("couldn't decode backend request");
let output = pt::BackendOutput {
value: Some(err.into()),
};
output.encode(&mut buf).expect("encode failed");
@ -62,21 +62,24 @@ impl Bridge {
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 {
match self.run_command_inner(ival) {
Ok(output) => output,
Err(err) => err.into(),
}
} 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> {
use pt::bridge_output::Value as OValue;
fn run_command_inner(
&self,
ival: pt::backend_input::Value,
) -> Result<pt::backend_output::Value> {
use pt::backend_output::Value as OValue;
Ok(match ival {
Value::TemplateRequirements(input) => {
OValue::TemplateRequirements(self.template_requirements(input)?)
@ -100,7 +103,7 @@ impl Bridge {
.map(|(name, ord)| (name.as_str(), *ord as u16))
.collect();
// 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
.template_front
.into_iter()

View File

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

View File

@ -1,5 +1,5 @@
[package]
name = "pybridge"
name = "pymod"
version = "0.1.0"
edition = "2018"
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::types::PyBytes;
#[pyclass]
struct Bridge {
bridge: RustBridge,
struct Backend {
backend: RustBackend,
}
#[pymethods]
impl Bridge {
impl Backend {
#[new]
fn init(obj: &PyRawObject) {
obj.init({
Bridge {
bridge: Default::default(),
Backend {
backend: Default::default(),
}
});
}
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);
Ok(out_obj.into())
}
@ -27,7 +27,7 @@ impl Bridge {
#[pymodule]
fn _ankirs(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Bridge>()?;
m.add_class::<Backend>()?;
Ok(())
}

View File

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