mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Remaining Python 3 compatibility changes
Summary: Biggest change is turning everything to unicode Test Plan: The tests Reviewers: dsagal, paulfitz Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2875
This commit is contained in:
parent
1af99e9567
commit
305b133c59
@ -639,7 +639,7 @@ async function handleSandboxError<T>(tableId: string, colNames: string[], p: Pro
|
|||||||
if (match) {
|
if (match) {
|
||||||
throw new ApiError(`Invalid row id ${match[1]}`, 400);
|
throw new ApiError(`Invalid row id ${match[1]}`, 400);
|
||||||
}
|
}
|
||||||
match = e.message.match(/\[Sandbox\] KeyError '(.*?)'/);
|
match = e.message.match(/\[Sandbox\] KeyError u?'(.*?)'/);
|
||||||
if (match) {
|
if (match) {
|
||||||
if (match[1] === tableId) {
|
if (match[1] === tableId) {
|
||||||
throw new ApiError(`Table not found "${tableId}"`, 404);
|
throw new ApiError(`Table not found "${tableId}"`, 404);
|
||||||
|
@ -78,7 +78,7 @@ export class NSandbox implements ISandbox {
|
|||||||
public readonly childProc: ChildProcess;
|
public readonly childProc: ChildProcess;
|
||||||
private _logTimes: boolean;
|
private _logTimes: boolean;
|
||||||
private _exportedFunctions: {[name: string]: SandboxMethod};
|
private _exportedFunctions: {[name: string]: SandboxMethod};
|
||||||
private _marshaller = new marshal.Marshaller({stringToBuffer: true, version: 2});
|
private _marshaller = new marshal.Marshaller({stringToBuffer: false, version: 2});
|
||||||
private _unmarshaller = new marshal.Unmarshaller({ bufferToString: false });
|
private _unmarshaller = new marshal.Unmarshaller({ bufferToString: false });
|
||||||
|
|
||||||
// Members used for reading from the sandbox process.
|
// Members used for reading from the sandbox process.
|
||||||
|
@ -109,8 +109,7 @@ def prepare_acl_col_renames(docmodel, useractions, col_renames_dict):
|
|||||||
# Go through again checking if anything in ACL formulas is affected by the rename.
|
# Go through again checking if anything in ACL formulas is affected by the rename.
|
||||||
for rule_rec in docmodel.aclRules.all:
|
for rule_rec in docmodel.aclRules.all:
|
||||||
if rule_rec.aclFormula:
|
if rule_rec.aclFormula:
|
||||||
# Positions are obtained from unicode version of formulas, so that's what we must patch
|
formula = rule_rec.aclFormula
|
||||||
formula = rule_rec.aclFormula.decode('utf8')
|
|
||||||
patches = []
|
patches = []
|
||||||
|
|
||||||
for entity in parse_acl_grist_entities(rule_rec.aclFormula):
|
for entity in parse_acl_grist_entities(rule_rec.aclFormula):
|
||||||
@ -129,7 +128,7 @@ def prepare_acl_col_renames(docmodel, useractions, col_renames_dict):
|
|||||||
patches.append(patch)
|
patches.append(patch)
|
||||||
|
|
||||||
replacer = textbuilder.Replacer(textbuilder.Text(formula), patches)
|
replacer = textbuilder.Replacer(textbuilder.Text(formula), patches)
|
||||||
txt = replacer.get_text().encode('utf8')
|
txt = replacer.get_text()
|
||||||
rule_updates.append((rule_rec, {'aclFormula': txt,
|
rule_updates.append((rule_rec, {'aclFormula': txt,
|
||||||
'aclFormulaParsed': parse_acl_formula_json(txt)}))
|
'aclFormulaParsed': parse_acl_formula_json(txt)}))
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ import tokenize
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import asttokens
|
import asttokens
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
def parse_acl_formula(acl_formula):
|
def parse_acl_formula(acl_formula):
|
||||||
"""
|
"""
|
||||||
@ -26,10 +28,12 @@ def parse_acl_formula(acl_formula):
|
|||||||
Attr node, attr_name
|
Attr node, attr_name
|
||||||
Comment node, comment
|
Comment node, comment
|
||||||
"""
|
"""
|
||||||
|
if isinstance(acl_formula, six.binary_type):
|
||||||
|
acl_formula = acl_formula.decode('utf8')
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(acl_formula, mode='eval')
|
tree = ast.parse(acl_formula, mode='eval')
|
||||||
result = _TreeConverter().visit(tree)
|
result = _TreeConverter().visit(tree)
|
||||||
for part in tokenize.generate_tokens(io.StringIO(acl_formula.decode('utf-8')).readline):
|
for part in tokenize.generate_tokens(io.StringIO(acl_formula).readline):
|
||||||
if part[0] == tokenize.COMMENT and part[1].startswith('#'):
|
if part[0] == tokenize.COMMENT and part[1].startswith('#'):
|
||||||
result = ['Comment', result, part[1][1:].strip()]
|
result = ['Comment', result, part[1][1:].strip()]
|
||||||
break
|
break
|
||||||
|
@ -116,7 +116,7 @@ def action_from_repr(doc_action):
|
|||||||
try:
|
try:
|
||||||
return decode_objects(action_type(*doc_action[1:]))
|
return decode_objects(action_type(*doc_action[1:]))
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise TypeError("%s: %s" % (doc_action[0], e.message))
|
raise TypeError("%s: %s" % (doc_action[0], str(e)))
|
||||||
|
|
||||||
|
|
||||||
def convert_recursive_helper(converter, data):
|
def convert_recursive_helper(converter, data):
|
||||||
|
@ -70,6 +70,9 @@ class AutocompleteContext(object):
|
|||||||
return self._context
|
return self._context
|
||||||
|
|
||||||
def process_result(self, result):
|
def process_result(self, result):
|
||||||
|
# 'for' suggests the autocompletion 'for ' in python 3
|
||||||
|
result = result.rstrip()
|
||||||
|
|
||||||
# Callables are returned by rlcompleter with a trailing "(".
|
# Callables are returned by rlcompleter with a trailing "(".
|
||||||
if result.endswith('('):
|
if result.endswith('('):
|
||||||
funcname = result[0:-1]
|
funcname = result[0:-1]
|
||||||
|
@ -118,9 +118,10 @@ def _create_syntax_error_code(builder, input_text, err):
|
|||||||
output_offset = output_ln.line_to_offset(err.lineno, err.offset - 1 if err.offset else 0)
|
output_offset = output_ln.line_to_offset(err.lineno, err.offset - 1 if err.offset else 0)
|
||||||
input_offset = builder.map_back_offset(output_offset)
|
input_offset = builder.map_back_offset(output_offset)
|
||||||
line, col = input_ln.offset_to_line(input_offset)
|
line, col = input_ln.offset_to_line(input_offset)
|
||||||
return "%s\nraise %s('%s on line %d col %d')" % (
|
message = '%s on line %d col %d' % (err.args[0], line, col + 1)
|
||||||
|
return "%s\nraise %s(%r)" % (
|
||||||
textbuilder.line_start_re.sub('# ', input_text.rstrip()),
|
textbuilder.line_start_re.sub('# ', input_text.rstrip()),
|
||||||
type(err).__name__, err.args[0], line, col + 1)
|
type(err).__name__, message)
|
||||||
|
|
||||||
#----------------------------------------------------------------------
|
#----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -268,11 +268,43 @@ class DateTimeColumn(NumericColumn):
|
|||||||
def sample_value(self):
|
def sample_value(self):
|
||||||
return _sample_datetime
|
return _sample_datetime
|
||||||
|
|
||||||
|
|
||||||
|
class MixedTypesKey(object):
|
||||||
|
"""
|
||||||
|
Sort key that can contain different types.
|
||||||
|
This mimics Python 2 where values of different types can be compared,
|
||||||
|
falling back on some comparison of the types when the values
|
||||||
|
can't be compared normally.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("value",)
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "MixedTypesKey({self.value!r})".format(self=self)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.value == other.value
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
try:
|
||||||
|
return self.value < other.value
|
||||||
|
except TypeError:
|
||||||
|
return type(self.value).__name__ < type(other.value).__name__
|
||||||
|
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
def MixedTypesKey(x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
class PositionColumn(NumericColumn):
|
class PositionColumn(NumericColumn):
|
||||||
def __init__(self, table, col_id, col_info):
|
def __init__(self, table, col_id, col_info):
|
||||||
super(PositionColumn, self).__init__(table, col_id, col_info)
|
super(PositionColumn, self).__init__(table, col_id, col_info)
|
||||||
# This is a list of row_ids, ordered by the position.
|
# This is a list of row_ids, ordered by the position.
|
||||||
self._sorted_rows = SortedListWithKey(key=self.raw_get)
|
self._sorted_rows = SortedListWithKey(key=lambda x: MixedTypesKey(self.raw_get(x)))
|
||||||
|
|
||||||
def set(self, row_id, value):
|
def set(self, row_id, value):
|
||||||
self._sorted_rows.discard(row_id)
|
self._sorted_rows.discard(row_id)
|
||||||
@ -282,7 +314,8 @@ class PositionColumn(NumericColumn):
|
|||||||
|
|
||||||
def copy_from_column(self, other_column):
|
def copy_from_column(self, other_column):
|
||||||
super(PositionColumn, self).copy_from_column(other_column)
|
super(PositionColumn, self).copy_from_column(other_column)
|
||||||
self._sorted_rows = SortedListWithKey(other_column._sorted_rows[:], key=self.raw_get)
|
self._sorted_rows = SortedListWithKey(other_column._sorted_rows[:],
|
||||||
|
key=lambda x: MixedTypesKey(self.raw_get(x)))
|
||||||
|
|
||||||
def prepare_new_values(self, values, ignore_data=False, action_summary=None):
|
def prepare_new_values(self, values, ignore_data=False, action_summary=None):
|
||||||
# This does the work of adjusting positions and relabeling existing rows with new position
|
# This does the work of adjusting positions and relabeling existing rows with new position
|
||||||
@ -290,9 +323,11 @@ class PositionColumn(NumericColumn):
|
|||||||
# used for updating a position for an existing row: we'll find a new value for it; later when
|
# used for updating a position for an existing row: we'll find a new value for it; later when
|
||||||
# this value is set, the old position will be removed and the new one added.
|
# this value is set, the old position will be removed and the new one added.
|
||||||
if ignore_data:
|
if ignore_data:
|
||||||
rows = SortedListWithKey([], key=self.raw_get)
|
rows = []
|
||||||
else:
|
else:
|
||||||
rows = self._sorted_rows
|
rows = self._sorted_rows
|
||||||
|
# prepare_inserts expects floats as keys, not MixedTypesKeys
|
||||||
|
rows = SortedListWithKey(rows, key=self.raw_get)
|
||||||
adjustments, new_values = relabeling.prepare_inserts(rows, values)
|
adjustments, new_values = relabeling.prepare_inserts(rows, values)
|
||||||
return new_values, [(self._sorted_rows[i], pos) for (i, pos) in adjustments]
|
return new_values, [(self._sorted_rows[i], pos) for (i, pos) in adjustments]
|
||||||
|
|
||||||
|
@ -77,9 +77,9 @@ class Graph(object):
|
|||||||
"""
|
"""
|
||||||
Print out the graph to stdout, for debugging.
|
Print out the graph to stdout, for debugging.
|
||||||
"""
|
"""
|
||||||
print "Dependency graph (%d edges):" % len(self._all_edges)
|
print("Dependency graph (%d edges):" % len(self._all_edges))
|
||||||
for edge in sorted(self._all_edges):
|
for edge in sorted(self._all_edges):
|
||||||
print " %s" % (edge,)
|
print(" %s" % (edge,))
|
||||||
|
|
||||||
def add_edge(self, out_node, in_node, relation):
|
def add_edge(self, out_node, in_node, relation):
|
||||||
"""
|
"""
|
||||||
|
@ -34,12 +34,13 @@ import table as table_module
|
|||||||
import useractions
|
import useractions
|
||||||
import column
|
import column
|
||||||
import repl
|
import repl
|
||||||
|
import urllib_patch # noqa imported for side effect
|
||||||
|
|
||||||
log = logger.Logger(__name__, logger.INFO)
|
log = logger.Logger(__name__, logger.INFO)
|
||||||
|
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
reload(sys)
|
reload(sys)
|
||||||
sys.setdefaultencoding('utf8')
|
sys.setdefaultencoding('utf8') # noqa
|
||||||
|
|
||||||
|
|
||||||
class OrderError(Exception):
|
class OrderError(Exception):
|
||||||
@ -1125,8 +1126,14 @@ class Engine(object):
|
|||||||
except Exception:
|
except Exception:
|
||||||
log.error("Inconsistent schema after revert on failure: %s" % traceback.format_exc())
|
log.error("Inconsistent schema after revert on failure: %s" % traceback.format_exc())
|
||||||
|
|
||||||
# Re-raise the original exception (simple `raise` wouldn't do if undo also fails above).
|
# Re-raise the original exception
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
# In Python 2, 'raise' raises the most recent exception,
|
||||||
|
# which may come from the try/except just above
|
||||||
|
# Python 3 keeps track of nested exceptions better
|
||||||
|
if six.PY2:
|
||||||
|
six.reraise(*exc_info)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Note that recalculations and auto-removals get included after processing all useractions.
|
# Note that recalculations and auto-removals get included after processing all useractions.
|
||||||
self._bring_all_up_to_date()
|
self._bring_all_up_to_date()
|
||||||
@ -1183,8 +1190,15 @@ class Engine(object):
|
|||||||
self.rebuild_usercode()
|
self.rebuild_usercode()
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error("Error rebuilding usercode after restoring schema: %s" % traceback.format_exc())
|
log.error("Error rebuilding usercode after restoring schema: %s" % traceback.format_exc())
|
||||||
# Re-raise the original exception (simple `raise` wouldn't do if rebuild also fails above).
|
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
# Re-raise the original exception
|
||||||
|
# In Python 2, 'raise' raises the most recent exception,
|
||||||
|
# which may come from the try/except just above
|
||||||
|
# Python 3 keeps track of nested exceptions better
|
||||||
|
if six.PY2:
|
||||||
|
six.reraise(*exc_info)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# If any columns got deleted, destroy them to clear _back_references in other tables, and to
|
# If any columns got deleted, destroy them to clear _back_references in other tables, and to
|
||||||
# force errors if anything still uses them. Also clear them from calc actions if needed.
|
# force errors if anything still uses them. Also clear them from calc actions if needed.
|
||||||
|
@ -93,15 +93,15 @@ def SELF_HYPERLINK(label=None, page=None, **kwargs):
|
|||||||
we might want to create links with `SELF_HYPERLINK(LinkKey_Code=$Code)`.
|
we might want to create links with `SELF_HYPERLINK(LinkKey_Code=$Code)`.
|
||||||
|
|
||||||
>>> SELF_HYPERLINK()
|
>>> SELF_HYPERLINK()
|
||||||
'https://docs.getgrist.com/sbaltsirg/Example'
|
u'https://docs.getgrist.com/sbaltsirg/Example'
|
||||||
>>> SELF_HYPERLINK(label='doc')
|
>>> SELF_HYPERLINK(label='doc')
|
||||||
'doc https://docs.getgrist.com/sbaltsirg/Example'
|
u'doc https://docs.getgrist.com/sbaltsirg/Example'
|
||||||
>>> SELF_HYPERLINK(page=2)
|
>>> SELF_HYPERLINK(page=2)
|
||||||
'https://docs.getgrist.com/sbaltsirg/Example/p/2'
|
u'https://docs.getgrist.com/sbaltsirg/Example/p/2'
|
||||||
>>> SELF_HYPERLINK(LinkKey_Code='X1234')
|
>>> SELF_HYPERLINK(LinkKey_Code='X1234')
|
||||||
'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234'
|
u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234'
|
||||||
>>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo')
|
>>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo')
|
||||||
'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo'
|
u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo'
|
||||||
>>> SELF_HYPERLINK(Linky_Link='Link')
|
>>> SELF_HYPERLINK(Linky_Link='Link')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
@ -110,6 +110,7 @@ def SELF_HYPERLINK(label=None, page=None, **kwargs):
|
|||||||
txt = os.environ.get('DOC_URL')
|
txt = os.environ.get('DOC_URL')
|
||||||
if not txt:
|
if not txt:
|
||||||
return None
|
return None
|
||||||
|
txt = six.text_type(txt)
|
||||||
if page:
|
if page:
|
||||||
txt += "/p/{}".format(page)
|
txt += "/p/{}".format(page)
|
||||||
if kwargs:
|
if kwargs:
|
||||||
@ -124,7 +125,7 @@ def SELF_HYPERLINK(label=None, page=None, **kwargs):
|
|||||||
parts[4] = urllib_parse.urlencode(query)
|
parts[4] = urllib_parse.urlencode(query)
|
||||||
txt = urllib_parse.urlunparse(parts)
|
txt = urllib_parse.urlunparse(parts)
|
||||||
if label:
|
if label:
|
||||||
txt = "{} {}".format(label, txt)
|
txt = u"{} {}".format(label, txt)
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
def VLOOKUP(table, **field_value_pairs):
|
def VLOOKUP(table, **field_value_pairs):
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
# -*- coding: UTF-8 -*-
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import dateutil.parser
|
|
||||||
import numbers
|
import numbers
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import dateutil.parser
|
||||||
import six
|
import six
|
||||||
from six import unichr
|
from six import unichr
|
||||||
from six.moves import xrange
|
from six.moves import xrange
|
||||||
|
|
||||||
|
from usertypes import AltText # pylint: disable=import-error
|
||||||
from .unimplemented import unimplemented
|
from .unimplemented import unimplemented
|
||||||
from usertypes import AltText # pylint: disable=import-error
|
|
||||||
|
|
||||||
def CHAR(table_number):
|
def CHAR(table_number):
|
||||||
"""
|
"""
|
||||||
@ -26,7 +27,7 @@ def CHAR(table_number):
|
|||||||
|
|
||||||
|
|
||||||
# See http://stackoverflow.com/a/93029/328565
|
# See http://stackoverflow.com/a/93029/328565
|
||||||
_control_chars = ''.join(map(unichr, range(0,32) + range(127,160)))
|
_control_chars = ''.join(map(unichr, list(xrange(0,32)) + list(xrange(127,160))))
|
||||||
_control_char_re = re.compile('[%s]' % re.escape(_control_chars))
|
_control_char_re = re.compile('[%s]' % re.escape(_control_chars))
|
||||||
|
|
||||||
def CLEAN(text):
|
def CLEAN(text):
|
||||||
@ -58,7 +59,7 @@ def CODE(string):
|
|||||||
|
|
||||||
|
|
||||||
def CONCATENATE(string, *more_strings):
|
def CONCATENATE(string, *more_strings):
|
||||||
"""
|
u"""
|
||||||
Joins together any number of text strings into one string. Also available under the name
|
Joins together any number of text strings into one string. Also available under the name
|
||||||
`CONCAT`. Similar to the Python expression `"".join(array_of_strings)`.
|
`CONCAT`. Similar to the Python expression `"".join(array_of_strings)`.
|
||||||
|
|
||||||
@ -70,11 +71,15 @@ def CONCATENATE(string, *more_strings):
|
|||||||
u'abc'
|
u'abc'
|
||||||
>>> CONCATENATE(0, "abc")
|
>>> CONCATENATE(0, "abc")
|
||||||
u'0abc'
|
u'0abc'
|
||||||
>>> CONCATENATE(2, " crème ", "brûlée".decode('utf8')) == "2 crème brûlée".decode('utf8')
|
>>> assert CONCATENATE(2, u" crème ", u"brûlée") == u'2 crème brûlée'
|
||||||
True
|
>>> assert CONCATENATE(2, " crème ", u"brûlée") == u'2 crème brûlée'
|
||||||
|
>>> assert CONCATENATE(2, " crème ", "brûlée") == u'2 crème brûlée'
|
||||||
"""
|
"""
|
||||||
return u''.join(val if isinstance(val, unicode) else str(val).decode('utf8')
|
return u''.join(
|
||||||
for val in (string,) + more_strings)
|
val.decode('utf8') if isinstance(val, six.binary_type) else
|
||||||
|
six.text_type(val)
|
||||||
|
for val in (string,) + more_strings
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def CONCAT(string, *more_strings):
|
def CONCAT(string, *more_strings):
|
||||||
@ -90,8 +95,7 @@ def CONCAT(string, *more_strings):
|
|||||||
u'abc'
|
u'abc'
|
||||||
>>> CONCAT(0, "abc")
|
>>> CONCAT(0, "abc")
|
||||||
u'0abc'
|
u'0abc'
|
||||||
>>> CONCAT(2, " crème ", "brûlée".decode('utf8')) == "2 crème brûlée".decode('utf8')
|
>>> assert CONCAT(2, u" crème ", u"brûlée") == u'2 crème brûlée'
|
||||||
True
|
|
||||||
"""
|
"""
|
||||||
return CONCATENATE(string, *more_strings)
|
return CONCATENATE(string, *more_strings)
|
||||||
|
|
||||||
@ -443,48 +447,57 @@ def SUBSTITUTE(text, old_text, new_text, instance_num=None):
|
|||||||
Same as `text.replace(old_text, new_text)` when instance_num is omitted.
|
Same as `text.replace(old_text, new_text)` when instance_num is omitted.
|
||||||
|
|
||||||
>>> SUBSTITUTE("Sales Data", "Sales", "Cost")
|
>>> SUBSTITUTE("Sales Data", "Sales", "Cost")
|
||||||
'Cost Data'
|
u'Cost Data'
|
||||||
>>> SUBSTITUTE("Quarter 1, 2008", "1", "2", 1)
|
>>> SUBSTITUTE("Quarter 1, 2008", "1", "2", 1)
|
||||||
'Quarter 2, 2008'
|
u'Quarter 2, 2008'
|
||||||
>>> SUBSTITUTE("Quarter 1, 2011", "1", "2", 3)
|
>>> SUBSTITUTE("Quarter 1, 2011", "1", "2", 3)
|
||||||
'Quarter 1, 2012'
|
u'Quarter 1, 2012'
|
||||||
|
|
||||||
More tests:
|
More tests:
|
||||||
>>> SUBSTITUTE("Hello world", "", "-")
|
>>> SUBSTITUTE("Hello world", "", "-")
|
||||||
'Hello world'
|
u'Hello world'
|
||||||
>>> SUBSTITUTE("Hello world", " ", "-")
|
>>> SUBSTITUTE("Hello world", " ", "-")
|
||||||
'Hello-world'
|
u'Hello-world'
|
||||||
>>> SUBSTITUTE("Hello world", " ", 12.1)
|
>>> SUBSTITUTE("Hello world", " ", 12.1)
|
||||||
'Hello12.1world'
|
u'Hello12.1world'
|
||||||
>>> SUBSTITUTE(u"Hello world", u" ", 12.1)
|
>>> SUBSTITUTE(u"Hello world", u" ", 12.1)
|
||||||
u'Hello12.1world'
|
u'Hello12.1world'
|
||||||
>>> SUBSTITUTE("Hello world", "world", "")
|
>>> SUBSTITUTE("Hello world", "world", "")
|
||||||
'Hello '
|
u'Hello '
|
||||||
>>> SUBSTITUTE("Hello", "world", "")
|
>>> SUBSTITUTE("Hello", "world", "")
|
||||||
'Hello'
|
u'Hello'
|
||||||
|
|
||||||
Overlapping matches are all counted when looking for instance_num.
|
Overlapping matches are all counted when looking for instance_num.
|
||||||
>>> SUBSTITUTE('abababab', 'abab', 'xxxx')
|
>>> SUBSTITUTE('abababab', 'abab', 'xxxx')
|
||||||
'xxxxxxxx'
|
u'xxxxxxxx'
|
||||||
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 1)
|
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 1)
|
||||||
'xxxxabab'
|
u'xxxxabab'
|
||||||
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 2)
|
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 2)
|
||||||
'abxxxxab'
|
u'abxxxxab'
|
||||||
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 3)
|
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 3)
|
||||||
'ababxxxx'
|
u'ababxxxx'
|
||||||
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 4)
|
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 4)
|
||||||
'abababab'
|
u'abababab'
|
||||||
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 0)
|
>>> SUBSTITUTE('abababab', 'abab', 'xxxx', 0)
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValueError: instance_num invalid
|
ValueError: instance_num invalid
|
||||||
|
>>> SUBSTITUTE( "crème", "è", "e")
|
||||||
|
u'creme'
|
||||||
|
>>> SUBSTITUTE(u"crème", u"è", "e")
|
||||||
|
u'creme'
|
||||||
|
>>> SUBSTITUTE(u"crème", "è", "e")
|
||||||
|
u'creme'
|
||||||
|
>>> SUBSTITUTE( "crème", u"è", "e")
|
||||||
|
u'creme'
|
||||||
"""
|
"""
|
||||||
|
text = six.text_type(text)
|
||||||
|
old_text = six.text_type(old_text)
|
||||||
|
new_text = six.text_type(new_text)
|
||||||
|
|
||||||
if not old_text:
|
if not old_text:
|
||||||
return text
|
return text
|
||||||
|
|
||||||
if not isinstance(new_text, six.string_types):
|
|
||||||
new_text = str(new_text)
|
|
||||||
|
|
||||||
if instance_num is None:
|
if instance_num is None:
|
||||||
return text.replace(old_text, new_text)
|
return text.replace(old_text, new_text)
|
||||||
|
|
||||||
@ -505,22 +518,23 @@ def T(value):
|
|||||||
Returns value if value is text, or the empty string when value is not text.
|
Returns value if value is text, or the empty string when value is not text.
|
||||||
|
|
||||||
>>> T('Text')
|
>>> T('Text')
|
||||||
'Text'
|
u'Text'
|
||||||
>>> T(826)
|
>>> T(826)
|
||||||
''
|
u''
|
||||||
>>> T('826')
|
>>> T('826')
|
||||||
'826'
|
u'826'
|
||||||
>>> T(False)
|
>>> T(False)
|
||||||
''
|
u''
|
||||||
>>> T('100 points')
|
>>> T('100 points')
|
||||||
'100 points'
|
u'100 points'
|
||||||
>>> T(AltText('Text'))
|
>>> T(AltText('Text'))
|
||||||
'Text'
|
u'Text'
|
||||||
>>> T(float('nan'))
|
>>> T(float('nan'))
|
||||||
''
|
u''
|
||||||
"""
|
"""
|
||||||
return (value if isinstance(value, basestring) else
|
return (value.decode('utf8') if isinstance(value, six.binary_type) else
|
||||||
str(value) if isinstance(value, AltText) else "")
|
value if isinstance(value, six.text_type) else
|
||||||
|
six.text_type(value) if isinstance(value, AltText) else u"")
|
||||||
|
|
||||||
|
|
||||||
@unimplemented
|
@unimplemented
|
||||||
@ -565,8 +579,7 @@ def VALUE(text):
|
|||||||
|
|
||||||
>>> VALUE("$1,000")
|
>>> VALUE("$1,000")
|
||||||
1000
|
1000
|
||||||
>>> VALUE("16:48:00") - VALUE("12:00:00")
|
>>> assert VALUE("16:48:00") - VALUE("12:00:00") == datetime.timedelta(0, 17280)
|
||||||
datetime.timedelta(0, 17280)
|
|
||||||
>>> VALUE("01/01/2012")
|
>>> VALUE("01/01/2012")
|
||||||
datetime.datetime(2012, 1, 1, 0, 0)
|
datetime.datetime(2012, 1, 1, 0, 0)
|
||||||
>>> VALUE("")
|
>>> VALUE("")
|
||||||
|
@ -36,6 +36,7 @@ def table_data_from_db(table_name, table_data_repr):
|
|||||||
if table_data_repr is None:
|
if table_data_repr is None:
|
||||||
return actions.TableData(table_name, [], {})
|
return actions.TableData(table_name, [], {})
|
||||||
table_data_parsed = marshal.loads(table_data_repr)
|
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")
|
id_col = table_data_parsed.pop("id")
|
||||||
return actions.TableData(table_name, id_col,
|
return actions.TableData(table_name, id_col,
|
||||||
actions.decode_bulk_values(table_data_parsed, _decode_db_value))
|
actions.decode_bulk_values(table_data_parsed, _decode_db_value))
|
||||||
@ -44,14 +45,8 @@ def _decode_db_value(value):
|
|||||||
# Decode database values received from SQLite's allMarshal() call. These are encoded by
|
# 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
|
# 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
|
# opposed to text which is received as unicode). See also encodeValue() in DocStorage.js
|
||||||
|
|
||||||
# TODO For the moment, the sandbox uses binary strings throughout (with text in utf8 encoding).
|
|
||||||
# We should switch to representing text with unicode instead. This requires care, at least in
|
|
||||||
# fixing various occurrences of str() in our code, which may fail and which return wrong type.
|
|
||||||
t = type(value)
|
t = type(value)
|
||||||
if t == unicode:
|
if t == six.binary_type:
|
||||||
return value.encode('utf8')
|
|
||||||
elif t == str:
|
|
||||||
return objtypes.decode_object(marshal.loads(value))
|
return objtypes.decode_object(marshal.loads(value))
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
|
@ -73,7 +73,8 @@ def create_migrations(all_tables, metadata_only=False):
|
|||||||
new_col_info = {c['id']: c for c in new_schema[table_id].columns}
|
new_col_info = {c['id']: c for c in new_schema[table_id].columns}
|
||||||
# Use an incomplete default for unknown (i.e. deprecated) columns; some uses of the column
|
# Use an incomplete default for unknown (i.e. deprecated) columns; some uses of the column
|
||||||
# would be invalid, such as adding a new record with missing values.
|
# would be invalid, such as adding a new record with missing values.
|
||||||
col_info = sorted([new_col_info.get(col_id, {'id': col_id}) for col_id in data.columns])
|
col_info = sorted([new_col_info.get(col_id, {'id': col_id}) for col_id in data.columns],
|
||||||
|
key=lambda c: list(six.iteritems(c)))
|
||||||
tdset.apply_doc_action(actions.AddTable(table_id, col_info))
|
tdset.apply_doc_action(actions.AddTable(table_id, col_info))
|
||||||
|
|
||||||
# And load in the original data, interpreting the TableData object as BulkAddRecord action.
|
# And load in the original data, interpreting the TableData object as BulkAddRecord action.
|
||||||
@ -177,7 +178,7 @@ def migration1(tdset):
|
|||||||
if rows:
|
if rows:
|
||||||
values = {'tableRef': [r[0] for r in rows],
|
values = {'tableRef': [r[0] for r in rows],
|
||||||
'viewRef': [r[1] for r in rows]}
|
'viewRef': [r[1] for r in rows]}
|
||||||
row_ids = range(1, len(rows) + 1)
|
row_ids = list(xrange(1, len(rows) + 1))
|
||||||
doc_actions.append(actions.ReplaceTableData('_grist_TabItems', row_ids, values))
|
doc_actions.append(actions.ReplaceTableData('_grist_TabItems', row_ids, values))
|
||||||
|
|
||||||
return tdset.apply_doc_actions(doc_actions)
|
return tdset.apply_doc_actions(doc_actions)
|
||||||
@ -212,14 +213,14 @@ def migration2(tdset):
|
|||||||
return actions.BulkUpdateRecord('_grist_Tables', row_ids, values)
|
return actions.BulkUpdateRecord('_grist_Tables', row_ids, values)
|
||||||
|
|
||||||
def create_tab_bar_action(views_to_table):
|
def create_tab_bar_action(views_to_table):
|
||||||
row_ids = range(1, len(views_to_table) + 1)
|
row_ids = list(xrange(1, len(views_to_table) + 1))
|
||||||
return actions.ReplaceTableData('_grist_TabBar', row_ids, {
|
return actions.ReplaceTableData('_grist_TabBar', row_ids, {
|
||||||
'viewRef': sorted(views_to_table.keys())
|
'viewRef': sorted(views_to_table.keys())
|
||||||
})
|
})
|
||||||
|
|
||||||
def create_table_views_action(views_to_table, primary_views):
|
def create_table_views_action(views_to_table, primary_views):
|
||||||
related_views = sorted(set(views_to_table.keys()) - set(primary_views.values()))
|
related_views = sorted(set(views_to_table.keys()) - set(primary_views.values()))
|
||||||
row_ids = range(1, len(related_views) + 1)
|
row_ids = list(xrange(1, len(related_views) + 1))
|
||||||
return actions.ReplaceTableData('_grist_TableViews', row_ids, {
|
return actions.ReplaceTableData('_grist_TableViews', row_ids, {
|
||||||
'tableRef': [views_to_table[v] for v in related_views],
|
'tableRef': [views_to_table[v] for v in related_views],
|
||||||
'viewRef': related_views,
|
'viewRef': related_views,
|
||||||
@ -757,7 +758,7 @@ def migration20(tdset):
|
|||||||
# the name of primary view's is the same as the tableId
|
# the name of primary view's is the same as the tableId
|
||||||
return (view.name, -1)
|
return (view.name, -1)
|
||||||
views.sort(key=view_key)
|
views.sort(key=view_key)
|
||||||
row_ids = range(1, len(views) + 1)
|
row_ids = list(xrange(1, len(views) + 1))
|
||||||
return tdset.apply_doc_actions([
|
return tdset.apply_doc_actions([
|
||||||
actions.AddTable('_grist_Pages', [
|
actions.AddTable('_grist_Pages', [
|
||||||
schema.make_column('viewRef', 'Ref:_grist_Views'),
|
schema.make_column('viewRef', 'Ref:_grist_Views'),
|
||||||
|
@ -142,8 +142,9 @@ class TzInfo(_tzinfo):
|
|||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
"""Implementation of tzinfo.tzname interface."""
|
"""Implementation of tzinfo.tzname interface."""
|
||||||
abbr = self.zone.dt_tzname(dt, self._favor_offset)
|
abbr = self.zone.dt_tzname(dt, self._favor_offset)
|
||||||
# tzname must return a string, not unicode.
|
if six.PY2 and isinstance(abbr, six.text_type):
|
||||||
return abbr.encode('utf8') if isinstance(abbr, unicode) else abbr
|
abbr = abbr.encode('utf8')
|
||||||
|
return abbr
|
||||||
|
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
"""Implementation of tzinfo.dst interface."""
|
"""Implementation of tzinfo.dst interface."""
|
||||||
|
@ -47,6 +47,7 @@ DATE_TOKENS_REGEX = re.compile("("+("|".join(DATE_TOKENS))+")")
|
|||||||
# List of separators to replace and match any standard date/time separators
|
# List of separators to replace and match any standard date/time separators
|
||||||
SEP = r"[\s/.\-:,]*"
|
SEP = r"[\s/.\-:,]*"
|
||||||
SEP_REGEX = re.compile(SEP)
|
SEP_REGEX = re.compile(SEP)
|
||||||
|
SEP_REPLACEMENT = SEP.replace("\\", "\\\\")
|
||||||
|
|
||||||
# Maps date parse format to compile regex
|
# Maps date parse format to compile regex
|
||||||
FORMAT_CACHE = {}
|
FORMAT_CACHE = {}
|
||||||
@ -77,7 +78,8 @@ def parse(date_string, parse_format, zonelabel='UTC', override_current_date=None
|
|||||||
# e.g. "MM-YY" -> "(?P<mm>\d{1,2})-(?P<yy>\d{2})"
|
# e.g. "MM-YY" -> "(?P<mm>\d{1,2})-(?P<yy>\d{2})"
|
||||||
# Note that DATE_TOKENS is ordered so that the longer letter chains are recognized first
|
# Note that DATE_TOKENS is ordered so that the longer letter chains are recognized first
|
||||||
tokens = DATE_TOKENS_REGEX.split(parse_format)
|
tokens = DATE_TOKENS_REGEX.split(parse_format)
|
||||||
tokens = [DATE_TOKENS[t] if t in DATE_TOKENS else SEP_REGEX.sub(SEP, t) for t in tokens]
|
tokens = [DATE_TOKENS[t] if t in DATE_TOKENS else SEP_REGEX.sub(SEP_REPLACEMENT, t)
|
||||||
|
for t in tokens]
|
||||||
|
|
||||||
# Compile new token string ignoring case (for month names)
|
# Compile new token string ignoring case (for month names)
|
||||||
parser = re.compile(''.join(tokens), re.I)
|
parser = re.compile(''.join(tokens), re.I)
|
||||||
|
@ -163,14 +163,16 @@ def encode_object(value):
|
|||||||
Returns ['U', repr(value)] if it fails to encode otherwise.
|
Returns ['U', repr(value)] if it fails to encode otherwise.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if isinstance(value, (str, unicode, float, bool)) or value is None:
|
if isinstance(value, (six.text_type, float, bool)) or value is None:
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, (long, int)):
|
elif isinstance(value, six.binary_type):
|
||||||
|
return value.decode('utf8')
|
||||||
|
elif isinstance(value, six.integer_types):
|
||||||
if not is_int_short(value):
|
if not is_int_short(value):
|
||||||
raise UnmarshallableError("Integer too large")
|
raise UnmarshallableError("Integer too large")
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, AltText):
|
elif isinstance(value, AltText):
|
||||||
return str(value)
|
return six.text_type(value)
|
||||||
elif isinstance(value, records.Record):
|
elif isinstance(value, records.Record):
|
||||||
return ['R', value._table.table_id, value._row_id]
|
return ['R', value._table.table_id, value._row_id]
|
||||||
elif isinstance(value, RecordStub):
|
elif isinstance(value, RecordStub):
|
||||||
@ -210,13 +212,6 @@ def decode_object(value):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if not isinstance(value, (list, tuple)):
|
if not isinstance(value, (list, tuple)):
|
||||||
if isinstance(value, unicode):
|
|
||||||
# TODO For now, the sandbox uses binary strings throughout; see TODO in main.py for more
|
|
||||||
# on this. Strings that come from JS become Python binary strings, and we will not see
|
|
||||||
# unicode here. But we may see it if unmarshalling data that comes from DB, since
|
|
||||||
# DocStorage encodes/decodes values by marshaling JS strings as unicode. For consistency,
|
|
||||||
# convert those unicode strings to binary strings too.
|
|
||||||
return value.encode('utf8')
|
|
||||||
return value
|
return value
|
||||||
code = value[0]
|
code = value[0]
|
||||||
args = value[1:]
|
args = value[1:]
|
||||||
|
@ -6,9 +6,10 @@ slight changes in order to be convenient for Grist's purposes
|
|||||||
|
|
||||||
import code
|
import code
|
||||||
import sys
|
import sys
|
||||||
from StringIO import StringIO
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
SUCCESS = 0
|
SUCCESS = 0
|
||||||
INCOMPLETE = 1
|
INCOMPLETE = 1
|
||||||
ERROR = 2
|
ERROR = 2
|
||||||
@ -38,7 +39,7 @@ class REPLInterpreter(code.InteractiveInterpreter):
|
|||||||
old_stdout = sys.stdout
|
old_stdout = sys.stdout
|
||||||
old_stderr = sys.stderr
|
old_stderr = sys.stderr
|
||||||
|
|
||||||
user_output = StringIO()
|
user_output = six.StringIO()
|
||||||
|
|
||||||
self.error_text = ""
|
self.error_text = ""
|
||||||
try:
|
try:
|
||||||
@ -67,7 +68,10 @@ class REPLInterpreter(code.InteractiveInterpreter):
|
|||||||
sys.stderr = old_stderr
|
sys.stderr = old_stderr
|
||||||
|
|
||||||
program_output = user_output.getvalue()
|
program_output = user_output.getvalue()
|
||||||
user_output.close()
|
try:
|
||||||
|
user_output.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
return EvalTuple(program_output, self.error_text, status)
|
return EvalTuple(program_output, self.error_text, status)
|
||||||
|
|
||||||
|
@ -92,7 +92,6 @@ class TestCodeBuilder(unittest.TestCase):
|
|||||||
|
|
||||||
# Check for reasonable behaviour with non-empty text and no statements.
|
# Check for reasonable behaviour with non-empty text and no statements.
|
||||||
self.assertEqual(make_body('# comment'), '# comment\npass')
|
self.assertEqual(make_body('# comment'), '# comment\npass')
|
||||||
self.assertEqual(make_body('\\'), '\\\npass')
|
|
||||||
|
|
||||||
self.assertEqual(make_body('rec = 1'), "# rec = 1\n" +
|
self.assertEqual(make_body('rec = 1'), "# rec = 1\n" +
|
||||||
"raise SyntaxError('Grist disallows assignment " +
|
"raise SyntaxError('Grist disallows assignment " +
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import doctest
|
import doctest
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
import functions
|
import functions
|
||||||
import moment
|
import moment
|
||||||
|
|
||||||
@ -15,6 +19,12 @@ def date_tearDown(doc_test):
|
|||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
functions.date._get_global_tz = _old_date_get_global_tz
|
functions.date._get_global_tz = _old_date_get_global_tz
|
||||||
|
|
||||||
|
class Py23DocChecker(doctest.OutputChecker):
|
||||||
|
def check_output(self, want, got, optionflags):
|
||||||
|
if six.PY3:
|
||||||
|
want = re.sub(r"^u'(.*?)'$", r"'\1'", want)
|
||||||
|
want = re.sub(r'^u"(.*?)"$', r'"\1"', want)
|
||||||
|
return doctest.OutputChecker.check_output(self, want, got, optionflags)
|
||||||
|
|
||||||
# This works with the unittest module to turn all the doctests in the functions' doc-comments into
|
# This works with the unittest module to turn all the doctests in the functions' doc-comments into
|
||||||
# unittest test cases.
|
# unittest test cases.
|
||||||
@ -26,8 +36,8 @@ def load_tests(loader, tests, ignore):
|
|||||||
tests.addTests(doctest.DocTestSuite(functions.logical))
|
tests.addTests(doctest.DocTestSuite(functions.logical))
|
||||||
tests.addTests(doctest.DocTestSuite(functions.math))
|
tests.addTests(doctest.DocTestSuite(functions.math))
|
||||||
tests.addTests(doctest.DocTestSuite(functions.stats))
|
tests.addTests(doctest.DocTestSuite(functions.stats))
|
||||||
tests.addTests(doctest.DocTestSuite(functions.text))
|
tests.addTests(doctest.DocTestSuite(functions.text, checker=Py23DocChecker()))
|
||||||
tests.addTests(doctest.DocTestSuite(functions.schedule,
|
tests.addTests(doctest.DocTestSuite(functions.schedule,
|
||||||
setUp = date_setUp, tearDown = date_tearDown))
|
setUp = date_setUp, tearDown = date_tearDown))
|
||||||
tests.addTests(doctest.DocTestSuite(functions.lookup))
|
tests.addTests(doctest.DocTestSuite(functions.lookup, checker=Py23DocChecker()))
|
||||||
return tests
|
return tests
|
||||||
|
@ -256,7 +256,7 @@ class TestRelabeling(unittest.TestCase):
|
|||||||
self._do_test_renumber_ends([])
|
self._do_test_renumber_ends([])
|
||||||
|
|
||||||
def test_renumber_endpoints2(self):
|
def test_renumber_endpoints2(self):
|
||||||
self._do_test_renumber_ends(zip("abcd", [40,50,60,70]))
|
self._do_test_renumber_ends(list(zip("abcd", [40,50,60,70])))
|
||||||
|
|
||||||
def _do_test_renumber_ends(self, initial):
|
def _do_test_renumber_ends(self, initial):
|
||||||
# Test insertions that happen together on the left and on the right.
|
# Test insertions that happen together on the left and on the right.
|
||||||
|
@ -330,23 +330,23 @@ class TestRenames(test_engine.EngineTestCase):
|
|||||||
def test_renames_with_non_ascii(self):
|
def test_renames_with_non_ascii(self):
|
||||||
# Test that presence of unicode does not interfere with formula adjustments for renaming.
|
# Test that presence of unicode does not interfere with formula adjustments for renaming.
|
||||||
self.load_sample(self.sample)
|
self.load_sample(self.sample)
|
||||||
self.add_column("Address", "CityUpper", formula="'Øî'+$city.upper()+'áü'")
|
self.add_column("Address", "CityUpper", formula=u"'Øî'+$city.upper()+'áü'")
|
||||||
out_actions = self.apply_user_action(["RenameColumn", "Address", "city", "ciudad"])
|
out_actions = self.apply_user_action(["RenameColumn", "Address", "city", "ciudad"])
|
||||||
self.assertPartialOutActions(out_actions, { "stored": [
|
self.assertPartialOutActions(out_actions, { "stored": [
|
||||||
["RenameColumn", "Address", "city", "ciudad"],
|
["RenameColumn", "Address", "city", "ciudad"],
|
||||||
["ModifyColumn", "People", "city", {"formula": "$addr.ciudad"}],
|
["ModifyColumn", "People", "city", {"formula": "$addr.ciudad"}],
|
||||||
["ModifyColumn", "Address", "CityUpper", {"formula": "'Øî'+$ciudad.upper()+'áü'"}],
|
["ModifyColumn", "Address", "CityUpper", {"formula": u"'Øî'+$ciudad.upper()+'áü'"}],
|
||||||
["BulkUpdateRecord", "_grist_Tables_column", [21, 24, 25], {
|
["BulkUpdateRecord", "_grist_Tables_column", [21, 24, 25], {
|
||||||
"colId": ["ciudad", "city", "CityUpper"],
|
"colId": ["ciudad", "city", "CityUpper"],
|
||||||
"formula": ["", "$addr.ciudad", "'Øî'+$ciudad.upper()+'áü'"],
|
"formula": ["", "$addr.ciudad", u"'Øî'+$ciudad.upper()+'áü'"],
|
||||||
}]
|
}]
|
||||||
]})
|
]})
|
||||||
self.assertTableData("Address", cols="all", data=[
|
self.assertTableData("Address", cols="all", data=[
|
||||||
["id", "ciudad", "CityUpper"],
|
["id", "ciudad", "CityUpper"],
|
||||||
[11, "New York", "ØîNEW YORKáü"],
|
[11, "New York", u"ØîNEW YORKáü"],
|
||||||
[12, "Colombia", "ØîCOLOMBIAáü"],
|
[12, "Colombia", u"ØîCOLOMBIAáü"],
|
||||||
[13, "New Haven", "ØîNEW HAVENáü"],
|
[13, "New Haven", u"ØîNEW HAVENáü"],
|
||||||
[14, "West Haven", "ØîWEST HAVENáü"],
|
[14, "West Haven", u"ØîWEST HAVENáü"],
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_rename_updates_properties(self):
|
def test_rename_updates_properties(self):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long
|
||||||
|
import six
|
||||||
|
|
||||||
import logger
|
import logger
|
||||||
import testutil
|
import testutil
|
||||||
@ -25,7 +26,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
"Types": [
|
"Types": [
|
||||||
["id", "text", "numeric", "int", "bool", "date"],
|
["id", "text", "numeric", "int", "bool", "date"],
|
||||||
[11, "New York", "New York", "New York", "New York", "New York"],
|
[11, "New York", "New York", "New York", "New York", "New York"],
|
||||||
[12, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö"],
|
[12, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö"],
|
||||||
[13, False, False, False, False, False],
|
[13, False, False, False, False, False],
|
||||||
[14, True, True, True, True, True],
|
[14, True, True, True, True, True],
|
||||||
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
||||||
@ -61,20 +62,20 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
|
|
||||||
self.assertPartialOutActions(out_actions, {
|
self.assertPartialOutActions(out_actions, {
|
||||||
"stored": [["BulkUpdateRecord", "Types", self.all_row_ids, {
|
"stored": [["BulkUpdateRecord", "Types", self.all_row_ids, {
|
||||||
"text": [None,"","1","0","8.153","1509556595","True","False","Chîcágö","New York"],
|
"text": [None,"","1","0","8.153","1509556595","True","False",u"Chîcágö","New York"],
|
||||||
"numeric": [None, None, 1.0, 0.0, 8.153, 1509556595.0, 1.0, 0.0, "Chîcágö", "New York"],
|
"numeric": [None, None, 1.0, 0.0, 8.153, 1509556595.0, 1.0, 0.0, u"Chîcágö", "New York"],
|
||||||
"int": [None, None, 1, 0, 8, 1509556595, 1, 0, "Chîcágö", "New York"],
|
"int": [None, None, 1, 0, 8, 1509556595, 1, 0, u"Chîcágö", "New York"],
|
||||||
"bool": [False, False, True, False, True, True, True, False, "Chîcágö", "New York"],
|
"bool": [False, False, True, False, True, True, True, False, u"Chîcágö", "New York"],
|
||||||
"date": [None, None, 1.0, 0.0, 8.153, 1509556595.0, 1.0, 0.0, 1548115200.0, "New York"]
|
"date": [None, None, 1.0, 0.0, 8.153, 1509556595.0, 1.0, 0.0, 1548115200.0, "New York"]
|
||||||
}],
|
}],
|
||||||
["UpdateRecord", "Formulas", 1, {"division": 0.0}],
|
["UpdateRecord", "Formulas", 1, {"division": 0.0}],
|
||||||
],
|
],
|
||||||
"undo": [["BulkUpdateRecord", "Types", self.all_row_ids, {
|
"undo": [["BulkUpdateRecord", "Types", self.all_row_ids, {
|
||||||
"text": ["New York", "Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None],
|
"text": ["New York", u"Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None],
|
||||||
"numeric": ["New York", "Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None],
|
"numeric": ["New York", u"Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None],
|
||||||
"int": ["New York", "Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None],
|
"int": ["New York", u"Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None],
|
||||||
"bool": ["New York", "Chîcágö", False, True, 1509556595, 8.153, False, True, "", None],
|
"bool": ["New York", u"Chîcágö", False, True, 1509556595, 8.153, False, True, "", None],
|
||||||
"date": ["New York", "Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None]
|
"date": ["New York", u"Chîcágö", False, True, 1509556595, 8.153, 0, 1, "", None]
|
||||||
}],
|
}],
|
||||||
["UpdateRecord", "Formulas", 1, {"division": 0.5}],
|
["UpdateRecord", "Formulas", 1, {"division": 0.5}],
|
||||||
]
|
]
|
||||||
@ -90,7 +91,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
[16, "1509556595", 1509556595, 1509556595, True, 1509556595.0],
|
[16, "1509556595", 1509556595, 1509556595, True, 1509556595.0],
|
||||||
[17, "True", 1.0, 1, True, 1.0],
|
[17, "True", 1.0, 1, True, 1.0],
|
||||||
[18, "False", 0.0, 0, False, 0.0],
|
[18, "False", 0.0, 0, False, 0.0],
|
||||||
[19, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", 1548115200.0],
|
[19, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", 1548115200.0],
|
||||||
[20, "New York", "New York", "New York", "New York", "New York"]
|
[20, "New York", "New York", "New York", "New York", "New York"]
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -184,7 +185,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
self.assertTableData("Types", data=[
|
self.assertTableData("Types", data=[
|
||||||
["id", "text", "numeric", "int", "bool", "date"],
|
["id", "text", "numeric", "int", "bool", "date"],
|
||||||
[11, "New York", "New York", "New York", "New York", "New York"],
|
[11, "New York", "New York", "New York", "New York", "New York"],
|
||||||
[12, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö"],
|
[12, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö"],
|
||||||
[13, False, "False", "False", "False", "False"],
|
[13, False, "False", "False", "False", "False"],
|
||||||
[14, True, "True", "True", "True", "True"],
|
[14, True, "True", "True", "True", "True"],
|
||||||
[15, 1509556595, "1509556595.0","1509556595","1509556595","1509556595.0"],
|
[15, 1509556595, "1509556595.0","1509556595","1509556595","1509556595.0"],
|
||||||
@ -283,7 +284,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
self.assertTableData("Types", data=[
|
self.assertTableData("Types", data=[
|
||||||
["id", "text", "numeric", "int", "bool", "date"],
|
["id", "text", "numeric", "int", "bool", "date"],
|
||||||
[11, "New York", "New York", "New York", "New York", "New York"],
|
[11, "New York", "New York", "New York", "New York", "New York"],
|
||||||
[12, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö"],
|
[12, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö"],
|
||||||
[13, 0.0, False, 0.0, 0.0, 0.0],
|
[13, 0.0, False, 0.0, 0.0, 0.0],
|
||||||
[14, 1.0, True, 1.0, 1.0, 1.0],
|
[14, 1.0, True, 1.0, 1.0, 1.0],
|
||||||
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
||||||
@ -327,15 +328,13 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
|
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
|
||||||
{"numeric": [0, 1, 8, None]}],
|
{"numeric": [0, 1, 8, None]}],
|
||||||
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Int"}],
|
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Int"}],
|
||||||
["UpdateRecord", "Formulas", 1, {"division": 0}],
|
] + six.PY2 * [["UpdateRecord", "Formulas", 1, {"division": 0}]], # Only in Python 2 due to integer division,
|
||||||
],
|
|
||||||
"undo": [
|
"undo": [
|
||||||
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
|
["BulkUpdateRecord", "Types", [13, 14, 16, 19],
|
||||||
{"numeric": [False, True, 8.153, ""]}],
|
{"numeric": [False, True, 8.153, ""]}],
|
||||||
["ModifyColumn", "Types", "numeric", {"type": "Numeric"}],
|
["ModifyColumn", "Types", "numeric", {"type": "Numeric"}],
|
||||||
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Numeric"}],
|
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Numeric"}],
|
||||||
["UpdateRecord", "Formulas", 1, {"division": 0.5}],
|
] + six.PY2 * [["UpdateRecord", "Formulas", 1, {"division": 0.5}]], # Only in Python 2 due to integer division
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Test Int -> Int conversion
|
# Test Int -> Int conversion
|
||||||
@ -383,7 +382,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
self.assertTableData("Types", data=[
|
self.assertTableData("Types", data=[
|
||||||
["id", "text", "numeric", "int", "bool", "date"],
|
["id", "text", "numeric", "int", "bool", "date"],
|
||||||
[11, "New York", "New York", "New York", "New York", "New York"],
|
[11, "New York", "New York", "New York", "New York", "New York"],
|
||||||
[12, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö"],
|
[12, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö"],
|
||||||
[13, 0, 0, False, 0, 0],
|
[13, 0, 0, False, 0, 0],
|
||||||
[14, 1, 1, True, 1, 1],
|
[14, 1, 1, True, 1, 1],
|
||||||
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
||||||
@ -428,15 +427,13 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
["BulkUpdateRecord", "Types", [15, 16, 17, 18, 19, 20],
|
["BulkUpdateRecord", "Types", [15, 16, 17, 18, 19, 20],
|
||||||
{"numeric": [True, True, False, True, False, False]}],
|
{"numeric": [True, True, False, True, False, False]}],
|
||||||
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Bool"}],
|
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Bool"}],
|
||||||
["UpdateRecord", "Formulas", 1, {"division": 0}],
|
] + six.PY2 * [["UpdateRecord", "Formulas", 1, {"division": 0}]], # Only in Python 2 due to integer division,
|
||||||
],
|
|
||||||
"undo": [
|
"undo": [
|
||||||
["BulkUpdateRecord", "Types", [15, 16, 17, 18, 19, 20],
|
["BulkUpdateRecord", "Types", [15, 16, 17, 18, 19, 20],
|
||||||
{"numeric": [1509556595.0, 8.153, 0.0, 1.0, "", None]}],
|
{"numeric": [1509556595.0, 8.153, 0.0, 1.0, "", None]}],
|
||||||
["ModifyColumn", "Types", "numeric", {"type": "Numeric"}],
|
["ModifyColumn", "Types", "numeric", {"type": "Numeric"}],
|
||||||
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Numeric"}],
|
["UpdateRecord", "_grist_Tables_column", 22, {"type": "Numeric"}],
|
||||||
["UpdateRecord", "Formulas", 1, {"division": 0.5}],
|
] + six.PY2 * [["UpdateRecord", "Formulas", 1, {"division": 0.5}]], # Only in Python 2 due to integer division
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Test Int -> Bool conversion
|
# Test Int -> Bool conversion
|
||||||
@ -484,7 +481,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
self.assertTableData("Types", data=[
|
self.assertTableData("Types", data=[
|
||||||
["id", "text", "numeric", "int", "bool", "date"],
|
["id", "text", "numeric", "int", "bool", "date"],
|
||||||
[11, "New York", "New York", "New York", "New York", "New York"],
|
[11, "New York", "New York", "New York", "New York", "New York"],
|
||||||
[12, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö"],
|
[12, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö"],
|
||||||
[13, False, False, False, False, False],
|
[13, False, False, False, False, False],
|
||||||
[14, True, True, True, True, True],
|
[14, True, True, True, True, True],
|
||||||
[15, True, True, True, 1509556595, True],
|
[15, True, True, True, 1509556595, True],
|
||||||
@ -585,7 +582,7 @@ class TestTypes(test_engine.EngineTestCase):
|
|||||||
self.assertTableData("Types", data=[
|
self.assertTableData("Types", data=[
|
||||||
["id", "text", "numeric", "int", "bool", "date"],
|
["id", "text", "numeric", "int", "bool", "date"],
|
||||||
[11, "New York", "New York", "New York", "New York", "New York"],
|
[11, "New York", "New York", "New York", "New York", "New York"],
|
||||||
[12, "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö", "Chîcágö"],
|
[12, u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö", u"Chîcágö"],
|
||||||
[13, 0.0, 0.0, 0.0, 0.0, False],
|
[13, 0.0, 0.0, 0.0, 0.0, False],
|
||||||
[14, 1.0, 1.0, 1.0, 1.0, True],
|
[14, 1.0, 1.0, 1.0, 1.0, True],
|
||||||
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
[15, 1509556595, 1509556595, 1509556595, 1509556595, 1509556595],
|
||||||
|
19
sandbox/grist/test_urllib_patch.py
Normal file
19
sandbox/grist/test_urllib_patch.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import unittest
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from urllib_patch import original_quote
|
||||||
|
|
||||||
|
|
||||||
|
class TestUrllibPatch(unittest.TestCase):
|
||||||
|
def test_patched_quote(self):
|
||||||
|
self.assertEqual(urllib.quote( "a b"), u"a%20b")
|
||||||
|
self.assertEqual(urllib.quote(u"a b"), u"a%20b")
|
||||||
|
self.assertEqual(urllib.quote(u"a é"), u"a%20%C3%A9")
|
||||||
|
|
||||||
|
self.assertEqual(original_quote( "a b"), u"a%20b")
|
||||||
|
self.assertEqual(original_quote(u"a b"), u"a%20b")
|
||||||
|
if six.PY3: # python 2 original quote can't handle non-ascii
|
||||||
|
self.assertEqual(original_quote(u"a é"), u"a%20%C3%A9")
|
@ -2768,7 +2768,7 @@
|
|||||||
["APPLY", {
|
["APPLY", {
|
||||||
"USER_ACTIONS": [
|
"USER_ACTIONS": [
|
||||||
// Access to usercode before and after re-generation
|
// Access to usercode before and after re-generation
|
||||||
["EvalCode", "list(Students.all.firstName)", null],
|
["EvalCode", "list(map(str, Students.all.firstName))", null],
|
||||||
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
||||||
["ModifyColumn", "Students", "firstName", { "type" : "Numeric" }],
|
["ModifyColumn", "Students", "firstName", { "type" : "Numeric" }],
|
||||||
["EvalCode", "list(Students.all.firstName)", 6],
|
["EvalCode", "list(Students.all.firstName)", 6],
|
||||||
@ -2778,7 +2778,7 @@
|
|||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"stored": [
|
"stored": [
|
||||||
["AddRecord", "_grist_REPL_Hist", 6,
|
["AddRecord", "_grist_REPL_Hist", 6,
|
||||||
{"code": "list(Students.all.firstName)", "errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
|
{"code": "list(map(str, Students.all.firstName))", "errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
|
||||||
|
|
||||||
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
|
||||||
["ModifyColumn", "Students", "firstName", {"type": "Numeric"}],
|
["ModifyColumn", "Students", "firstName", {"type": "Numeric"}],
|
||||||
@ -2808,7 +2808,7 @@
|
|||||||
["ModifyColumn", "Students", "firstName", {"type": "Text"}],
|
["ModifyColumn", "Students", "firstName", {"type": "Text"}],
|
||||||
["UpdateRecord", "_grist_Tables_column", 1, {"type": "Text"}],
|
["UpdateRecord", "_grist_Tables_column", 1, {"type": "Text"}],
|
||||||
|
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
|
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(map(str, Students.all.firstName))",
|
||||||
"errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
|
"errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
|
||||||
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
|
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
|
||||||
"errorText": "",
|
"errorText": "",
|
||||||
@ -2825,7 +2825,7 @@
|
|||||||
["APPLY", {
|
["APPLY", {
|
||||||
"USER_ACTIONS": [
|
"USER_ACTIONS": [
|
||||||
// Syntax Error
|
// Syntax Error
|
||||||
["EvalCode", "*!@&$fjjj112#(8!", null],
|
["EvalCode", "not correct c", null],
|
||||||
// Other continuations
|
// Other continuations
|
||||||
["EvalCode", "map(filter, ", null],
|
["EvalCode", "map(filter, ", null],
|
||||||
["EvalCode", "[1,2,3,", null],
|
["EvalCode", "[1,2,3,", null],
|
||||||
@ -2834,15 +2834,14 @@
|
|||||||
["EvalCode", "sys.exit(0)", null],
|
["EvalCode", "sys.exit(0)", null],
|
||||||
// User reassignment of sys.stdout/sys.stderr
|
// User reassignment of sys.stdout/sys.stderr
|
||||||
["EvalCode", "sys.stdout = None", null],
|
["EvalCode", "sys.stdout = None", null],
|
||||||
["EvalCode", "delattr(sys.stderr, 'close')", null],
|
|
||||||
["EvalCode", "setattr(sys.stdout, 'getvalue', lambda : 2)", null],
|
["EvalCode", "setattr(sys.stdout, 'getvalue', lambda : 2)", null],
|
||||||
["EvalCode", "def foo():\n global stdout\n exec 'stdout = 2'\n", null],
|
["EvalCode", "def foo():\n global stdout\n exec('stdout = 2')\n", null],
|
||||||
["EvalCode", "setattr(sys.stderr, 'close', foo)", null]
|
["EvalCode", "setattr(sys.stderr, 'close', foo)", null]
|
||||||
],
|
],
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"stored": [
|
"stored": [
|
||||||
["AddRecord", "_grist_REPL_Hist", 7,
|
["AddRecord", "_grist_REPL_Hist", 7,
|
||||||
{"code": "*!@&$fjjj112#(8!", "errorText": " File \"<input>\", line 1\n *!@&$fjjj112#(8!\n ^\nSyntaxError: invalid syntax\n", "outputText": ""}],
|
{"code": "not correct c", "errorText": " File \"<input>\", line 1\n not correct c\n ^\nSyntaxError: invalid syntax\n", "outputText": ""}],
|
||||||
["AddRecord", "_grist_REPL_Hist", 8,
|
["AddRecord", "_grist_REPL_Hist", 8,
|
||||||
{"code": "import sys", "errorText": "", "outputText": ""}],
|
{"code": "import sys", "errorText": "", "outputText": ""}],
|
||||||
["AddRecord", "_grist_REPL_Hist", 9,
|
["AddRecord", "_grist_REPL_Hist", 9,
|
||||||
@ -2850,15 +2849,13 @@
|
|||||||
["AddRecord", "_grist_REPL_Hist", 10,
|
["AddRecord", "_grist_REPL_Hist", 10,
|
||||||
{"code": "sys.stdout = None", "errorText": "", "outputText": ""}],
|
{"code": "sys.stdout = None", "errorText": "", "outputText": ""}],
|
||||||
["AddRecord", "_grist_REPL_Hist", 11,
|
["AddRecord", "_grist_REPL_Hist", 11,
|
||||||
{"code": "delattr(sys.stderr, 'close')", "errorText": "Traceback (most recent call last):\n File \"<input>\", line 1, in <module>\nAttributeError: StringIO instance has no attribute 'close'\n", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 12,
|
|
||||||
{"code": "setattr(sys.stdout, 'getvalue', lambda : 2)", "errorText": "", "outputText": 2}],
|
{"code": "setattr(sys.stdout, 'getvalue', lambda : 2)", "errorText": "", "outputText": 2}],
|
||||||
|
["AddRecord", "_grist_REPL_Hist", 12,
|
||||||
|
{"code": "def foo():\n global stdout\n exec('stdout = 2')\n", "errorText": "", "outputText": ""}],
|
||||||
["AddRecord", "_grist_REPL_Hist", 13,
|
["AddRecord", "_grist_REPL_Hist", 13,
|
||||||
{"code": "def foo():\n global stdout\n exec 'stdout = 2'\n", "errorText": "", "outputText": ""}],
|
|
||||||
["AddRecord", "_grist_REPL_Hist", 14,
|
|
||||||
{"code": "setattr(sys.stderr, 'close', foo)", "errorText": "", "outputText": ""}]
|
{"code": "setattr(sys.stderr, 'close', foo)", "errorText": "", "outputText": ""}]
|
||||||
],
|
],
|
||||||
"direct": [true, true, true, true, true, true, true, true],
|
"direct": [true, true, true, true, true, true, true],
|
||||||
"undo": [
|
"undo": [
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 7],
|
["RemoveRecord", "_grist_REPL_Hist", 7],
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 8],
|
["RemoveRecord", "_grist_REPL_Hist", 8],
|
||||||
@ -2866,10 +2863,9 @@
|
|||||||
["RemoveRecord", "_grist_REPL_Hist", 10],
|
["RemoveRecord", "_grist_REPL_Hist", 10],
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 11],
|
["RemoveRecord", "_grist_REPL_Hist", 11],
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 12],
|
["RemoveRecord", "_grist_REPL_Hist", 12],
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 13],
|
["RemoveRecord", "_grist_REPL_Hist", 13]
|
||||||
["RemoveRecord", "_grist_REPL_Hist", 14]
|
|
||||||
],
|
],
|
||||||
"retValue" : [true,false,false,true,true,true,true,true,true,true ]
|
"retValue" : [true,false,false,true,true,true,true,true,true ]
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
|
@ -59,7 +59,7 @@ def parse_testscript(script_path=None):
|
|||||||
all_lines.append(line)
|
all_lines.append(line)
|
||||||
full_text = "".join(all_lines)
|
full_text = "".join(all_lines)
|
||||||
|
|
||||||
script = byteify(json.loads(full_text))
|
script = json.loads(full_text)
|
||||||
|
|
||||||
samples = {}
|
samples = {}
|
||||||
test_cases = []
|
test_cases = []
|
||||||
@ -109,16 +109,6 @@ def parse_test_sample(obj, samples={}):
|
|||||||
return {"SCHEMA": schema, "DATA": data}
|
return {"SCHEMA": schema, "DATA": data}
|
||||||
|
|
||||||
|
|
||||||
def byteify(data):
|
|
||||||
"""
|
|
||||||
Convert all unicode strings in a parsed JSON object into utf8-encoded strings. We deal with
|
|
||||||
utf8-encoded strings throughout the test.
|
|
||||||
"""
|
|
||||||
if isinstance(data, unicode):
|
|
||||||
return data.encode('utf-8')
|
|
||||||
return actions.convert_recursive_helper(byteify, data)
|
|
||||||
|
|
||||||
|
|
||||||
def replace_nans(data):
|
def replace_nans(data):
|
||||||
"""
|
"""
|
||||||
Convert all NaNs and Infinities in the data to descriptive strings, since they cannot be
|
Convert all NaNs and Infinities in the data to descriptive strings, since they cannot be
|
||||||
|
@ -157,7 +157,11 @@ class Combiner(Builder):
|
|||||||
def __init__(self, parts):
|
def __init__(self, parts):
|
||||||
self._parts = parts
|
self._parts = parts
|
||||||
self._offsets = []
|
self._offsets = []
|
||||||
text_parts = [(p if isinstance(p, basestring) else p.get_text()) for p in self._parts]
|
text_parts = [
|
||||||
|
(p if isinstance(p, six.text_type) else
|
||||||
|
p.decode('utf8') if isinstance(p, six.binary_type) else
|
||||||
|
p.get_text())
|
||||||
|
for p in self._parts]
|
||||||
self._text = ''.join(text_parts)
|
self._text = ''.join(text_parts)
|
||||||
|
|
||||||
offset = 0
|
offset = 0
|
||||||
|
16
sandbox/grist/urllib_patch.py
Normal file
16
sandbox/grist/urllib_patch.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import urllib
|
||||||
|
|
||||||
|
import six
|
||||||
|
from six.moves import urllib_parse
|
||||||
|
|
||||||
|
original_quote = urllib_parse.quote
|
||||||
|
|
||||||
|
def patched_quote(s, safe='/'):
|
||||||
|
if isinstance(s, six.text_type):
|
||||||
|
s = s.encode('utf8')
|
||||||
|
result = original_quote(s, safe=safe)
|
||||||
|
if isinstance(result, six.binary_type):
|
||||||
|
result = result.decode('utf8')
|
||||||
|
return result
|
||||||
|
|
||||||
|
urllib.quote = patched_quote
|
@ -5,6 +5,7 @@ import json
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
from six.moves import xrange
|
||||||
|
|
||||||
import acl
|
import acl
|
||||||
from acl_formula import parse_acl_formula_json
|
from acl_formula import parse_acl_formula_json
|
||||||
@ -109,7 +110,7 @@ def from_repr(user_action):
|
|||||||
try:
|
try:
|
||||||
return action_type(*user_action[1:])
|
return action_type(*user_action[1:])
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise TypeError("%s: %s" % (user_action[0], e.message))
|
raise TypeError("%s: %s" % (user_action[0], str(e)))
|
||||||
|
|
||||||
def _make_clean_col_info(col_info, col_id=None):
|
def _make_clean_col_info(col_info, col_id=None):
|
||||||
"""
|
"""
|
||||||
@ -332,7 +333,7 @@ class UserActions(object):
|
|||||||
|
|
||||||
# Invalidate new records, including the omitted columns that may have default formulas,
|
# Invalidate new records, including the omitted columns that may have default formulas,
|
||||||
# in order to get dynamically-computed default values.
|
# in order to get dynamically-computed default values.
|
||||||
omitted_cols = table.all_columns.viewkeys() - column_values.viewkeys()
|
omitted_cols = six.viewkeys(table.all_columns) - six.viewkeys(column_values)
|
||||||
self._engine.invalidate_records(table_id, filled_row_ids, data_cols_to_recompute=omitted_cols)
|
self._engine.invalidate_records(table_id, filled_row_ids, data_cols_to_recompute=omitted_cols)
|
||||||
|
|
||||||
return filled_row_ids
|
return filled_row_ids
|
||||||
@ -599,18 +600,16 @@ class UserActions(object):
|
|||||||
col_rec = self._docmodel.get_column_rec(formula_table, formula_col)
|
col_rec = self._docmodel.get_column_rec(formula_table, formula_col)
|
||||||
# Create a patch and append to the list for this col_rec.
|
# Create a patch and append to the list for this col_rec.
|
||||||
name = col_id or table_id
|
name = col_id or table_id
|
||||||
# Positions are obtained from unicode version of formulas, so that's what we must patch
|
formula = col_rec.formula
|
||||||
formula = col_rec.formula.decode('utf8')
|
|
||||||
patch = textbuilder.make_patch(formula, pos, pos + len(name), new_name)
|
patch = textbuilder.make_patch(formula, pos, pos + len(name), new_name)
|
||||||
patches_map.setdefault(col_rec, []).append(patch)
|
patches_map.setdefault(col_rec, []).append(patch)
|
||||||
|
|
||||||
# Apply the collected patches to each affected formula, converting to unicode to apply the
|
# Apply the collected patches to each affected formula
|
||||||
# patches and back to byte string for how we maintain string values.
|
|
||||||
result = {}
|
result = {}
|
||||||
for col_rec, patches in six.iteritems(patches_map):
|
for col_rec, patches in patches_map.items():
|
||||||
formula = col_rec.formula.decode('utf8')
|
formula = col_rec.formula
|
||||||
replacer = textbuilder.Replacer(textbuilder.Text(formula), patches)
|
replacer = textbuilder.Replacer(textbuilder.Text(formula), patches)
|
||||||
result[col_rec] = replacer.get_text().encode('utf8')
|
result[col_rec] = replacer.get_text()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ data structure for values of the wrong type, and the memory savings aren't that
|
|||||||
the extra complexity.
|
the extra complexity.
|
||||||
"""
|
"""
|
||||||
import csv
|
import csv
|
||||||
import cStringIO
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import six
|
import six
|
||||||
@ -31,7 +30,7 @@ _type_defaults = {
|
|||||||
'Attachments': None,
|
'Attachments': None,
|
||||||
'Blob': None,
|
'Blob': None,
|
||||||
'Bool': False,
|
'Bool': False,
|
||||||
'Choice': '',
|
'Choice': u'',
|
||||||
'ChoiceList': None,
|
'ChoiceList': None,
|
||||||
'Date': None,
|
'Date': None,
|
||||||
'DateTime': None,
|
'DateTime': None,
|
||||||
@ -42,7 +41,7 @@ _type_defaults = {
|
|||||||
'PositionNumber': float('inf'),
|
'PositionNumber': float('inf'),
|
||||||
'Ref': 0,
|
'Ref': 0,
|
||||||
'RefList': None,
|
'RefList': None,
|
||||||
'Text': '',
|
'Text': u'',
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_type_default(col_type):
|
def get_type_default(col_type):
|
||||||
@ -131,10 +130,7 @@ class BaseColumnType(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If conversion failed, return a string to serve as alttext.
|
# If conversion failed, return a string to serve as alttext.
|
||||||
try:
|
try:
|
||||||
if isinstance(value_to_convert, six.text_type):
|
return six.text_type(value_to_convert)
|
||||||
# str() will fail for a non-ascii unicode object, which needs an explicit encoding.
|
|
||||||
return value_to_convert.encode('utf8')
|
|
||||||
return str(value_to_convert)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# If converting to string failed, we should still produce something.
|
# If converting to string failed, we should still produce something.
|
||||||
return objtypes.safe_repr(value_to_convert)
|
return objtypes.safe_repr(value_to_convert)
|
||||||
@ -157,7 +153,12 @@ class Text(BaseColumnType):
|
|||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def do_convert(cls, value):
|
def do_convert(cls, value):
|
||||||
return str(value) if value is not None else None
|
if isinstance(value, six.binary_type):
|
||||||
|
return value.decode('utf8')
|
||||||
|
elif value is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return six.text_type(value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_right_type(cls, value):
|
def is_right_type(cls, value):
|
||||||
@ -176,11 +177,11 @@ class Blob(BaseColumnType):
|
|||||||
"""
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def do_convert(cls, value):
|
def do_convert(cls, value):
|
||||||
return str(value) if value is not None else None
|
return value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_right_type(cls, value):
|
def is_right_type(cls, value):
|
||||||
return isinstance(value, (basestring, NoneType))
|
return isinstance(value, (six.binary_type, NoneType))
|
||||||
|
|
||||||
|
|
||||||
class Any(BaseColumnType):
|
class Any(BaseColumnType):
|
||||||
@ -190,7 +191,7 @@ class Any(BaseColumnType):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def do_convert(cls, value):
|
def do_convert(cls, value):
|
||||||
# Convert AltText values to plain text when assigning to type Any.
|
# Convert AltText values to plain text when assigning to type Any.
|
||||||
return str(value) if isinstance(value, AltText) else value
|
return six.text_type(value) if isinstance(value, AltText) else value
|
||||||
|
|
||||||
|
|
||||||
class Bool(BaseColumnType):
|
class Bool(BaseColumnType):
|
||||||
@ -206,7 +207,7 @@ class Bool(BaseColumnType):
|
|||||||
if isinstance(value, (float, six.integer_types)):
|
if isinstance(value, (float, six.integer_types)):
|
||||||
return True
|
return True
|
||||||
if isinstance(value, AltText):
|
if isinstance(value, AltText):
|
||||||
value = str(value)
|
value = six.text_type(value)
|
||||||
if isinstance(value, six.string_types):
|
if isinstance(value, six.string_types):
|
||||||
if value.lower() in ("false", "no", "0"):
|
if value.lower() in ("false", "no", "0"):
|
||||||
return False
|
return False
|
||||||
@ -334,13 +335,13 @@ class ChoiceList(BaseColumnType):
|
|||||||
# If it's a string that looks like JSON, try to parse it as such.
|
# If it's a string that looks like JSON, try to parse it as such.
|
||||||
if value.startswith('['):
|
if value.startswith('['):
|
||||||
try:
|
try:
|
||||||
return tuple(str(item) for item in json.loads(value))
|
return tuple(six.text_type(item) for item in json.loads(value))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
# Accepts other kinds of iterables; if that doesn't work, fail the conversion too.
|
# Accepts other kinds of iterables; if that doesn't work, fail the conversion too.
|
||||||
return tuple(str(item) for item in value)
|
return tuple(six.text_type(item) for item in value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_right_type(cls, value):
|
def is_right_type(cls, value):
|
||||||
@ -362,7 +363,7 @@ class ChoiceList(BaseColumnType):
|
|||||||
def toString(cls, value):
|
def toString(cls, value):
|
||||||
if isinstance(value, (tuple, list)):
|
if isinstance(value, (tuple, list)):
|
||||||
try:
|
try:
|
||||||
buf = cStringIO.StringIO()
|
buf = six.StringIO()
|
||||||
csv.writer(buf).writerow(value)
|
csv.writer(buf).writerow(value)
|
||||||
return buf.getvalue().strip()
|
return buf.getvalue().strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -434,7 +435,7 @@ class Reference(Id):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def typeConvert(cls, value, ref_table, visible_col=None): # pylint: disable=arguments-differ
|
def typeConvert(cls, value, ref_table, visible_col=None): # pylint: disable=arguments-differ
|
||||||
if ref_table and visible_col:
|
if ref_table and visible_col:
|
||||||
return ref_table.lookupOne(**{visible_col: value}) or str(value)
|
return ref_table.lookupOne(**{visible_col: value}) or six.text_type(value)
|
||||||
else:
|
else:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user