mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
4222f1ed32
Summary: This switches to using stdin/stdout for RPC calls to the sandbox, rather than specially allocated side channels. Plain text error information remains on stderr. The motivation for the change is to simplify use of sandboxes, some of which support extra file descriptors and some of which don't. The new style of communication is made the default, but I'm not committed to this, just that it be easy to switch to if needed. It is possible I'll need to switch the communication method again in the near future. One reason not to make this default would be windows support, which is likely broken since stdin/stdout are by default in text mode. Test Plan: existing tests pass Reviewers: dsagal, alexmojaki Reviewed By: dsagal, alexmojaki Differential Revision: https://phab.getgrist.com/D2897
122 lines
3.7 KiB
Python
122 lines
3.7 KiB
Python
"""
|
|
This module defines what sandbox functions are made available to the Node controller,
|
|
and starts the grist sandbox. See engine.py for the API documentation.
|
|
"""
|
|
import sys
|
|
sys.path.append('thirdparty')
|
|
# pylint: disable=wrong-import-position
|
|
|
|
import marshal
|
|
import functools
|
|
|
|
import six
|
|
|
|
from acl_formula import parse_acl_formula
|
|
import actions
|
|
from sandbox import get_default_sandbox
|
|
import engine
|
|
import migrations
|
|
import schema
|
|
import useractions
|
|
import objtypes
|
|
|
|
import logger
|
|
log = logger.Logger(__name__, logger.INFO)
|
|
|
|
def table_data_from_db(table_name, table_data_repr):
|
|
if table_data_repr is None:
|
|
return actions.TableData(table_name, [], {})
|
|
table_data_parsed = marshal.loads(table_data_repr)
|
|
table_data_parsed = {key.decode("utf8"): value for key, value in table_data_parsed.items()}
|
|
id_col = table_data_parsed.pop("id")
|
|
return actions.TableData(table_name, id_col,
|
|
actions.decode_bulk_values(table_data_parsed, _decode_db_value))
|
|
|
|
def _decode_db_value(value):
|
|
# Decode database values received from SQLite's allMarshal() call. These are encoded by
|
|
# marshalling certain types and storing as BLOBs (received in Python as binary strings, as
|
|
# opposed to text which is received as unicode). See also encodeValue() in DocStorage.js
|
|
t = type(value)
|
|
if t == six.binary_type:
|
|
return objtypes.decode_object(marshal.loads(value))
|
|
else:
|
|
return value
|
|
|
|
def run(sandbox):
|
|
eng = engine.Engine()
|
|
|
|
def export(method):
|
|
# Wrap each method so that it logs a message that it's being called.
|
|
@functools.wraps(method)
|
|
def wrapper(*args, **kwargs):
|
|
log.debug("calling %s" % method.__name__)
|
|
return method(*args, **kwargs)
|
|
|
|
sandbox.register(method.__name__, wrapper)
|
|
|
|
@export
|
|
def apply_user_actions(action_reprs):
|
|
action_group = eng.apply_user_actions([useractions.from_repr(u) for u in action_reprs])
|
|
return eng.acl_split(action_group).to_json_obj()
|
|
|
|
@export
|
|
def fetch_table(table_id, formulas=True, query=None):
|
|
return actions.get_action_repr(eng.fetch_table(table_id, formulas=formulas, query=query))
|
|
|
|
@export
|
|
def fetch_table_schema():
|
|
return eng.fetch_table_schema()
|
|
|
|
@export
|
|
def fetch_snapshot():
|
|
action_group = eng.fetch_snapshot()
|
|
return eng.acl_split(action_group).to_json_obj()
|
|
|
|
@export
|
|
def autocomplete(txt, table_id):
|
|
return eng.autocomplete(txt, table_id)
|
|
|
|
@export
|
|
def find_col_from_values(values, n, opt_table_id):
|
|
return eng.find_col_from_values(values, n, opt_table_id)
|
|
|
|
@export
|
|
def fetch_meta_tables(formulas=True):
|
|
return {table_id: actions.get_action_repr(table_data)
|
|
for (table_id, table_data) in six.iteritems(eng.fetch_meta_tables(formulas))}
|
|
|
|
@export
|
|
def load_meta_tables(meta_tables, meta_columns):
|
|
return eng.load_meta_tables(table_data_from_db("_grist_Tables", meta_tables),
|
|
table_data_from_db("_grist_Tables_column", meta_columns))
|
|
|
|
@export
|
|
def load_table(table_name, table_data):
|
|
return eng.load_table(table_data_from_db(table_name, table_data))
|
|
|
|
@export
|
|
def create_migrations(all_tables, metadata_only=False):
|
|
doc_actions = migrations.create_migrations(
|
|
{t: table_data_from_db(t, data) for t, data in six.iteritems(all_tables)}, metadata_only)
|
|
return [actions.get_action_repr(action) for action in doc_actions]
|
|
|
|
@export
|
|
def get_version():
|
|
return schema.SCHEMA_VERSION
|
|
|
|
@export
|
|
def get_formula_error(table_id, col_id, row_id):
|
|
return objtypes.encode_object(eng.get_formula_error(table_id, col_id, row_id))
|
|
|
|
export(parse_acl_formula)
|
|
export(eng.load_empty)
|
|
export(eng.load_done)
|
|
|
|
sandbox.run()
|
|
|
|
def main():
|
|
run(get_default_sandbox())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|