Adds decorator to create properties

With properties it is possible to have expressions like
$expr.foo handled by a function that trades $expr for 'foo'
property value.

This is done by 2 pieces:
1) #operator_. that handles $xpr.keyword expressions
that could not be handled by other registered operator
overloads and convert it to #property#keyword($expr)
dynamically constructed function name

2) Decorator that transforms func(source) into a
registered in context

Because property name is part of a called function name
there is no need to have separate #operator_. implementation
for all possible combinations of (type, property_name) and
makes property name be used in a context lookup thus
speeding it up

Change-Id: I8c39715d425d991cb2c1726e83c415f401cfd8fe
This commit is contained in:
Stan Lagun 2015-09-05 22:16:26 +03:00
parent 1218f07c5d
commit 86ca4124a5
5 changed files with 43 additions and 2 deletions

View File

@ -72,6 +72,8 @@ def create_context(data=utils.NO_VALUE, context=None, system=True,
context = _setup_context(data, context, finalizer, convention)
if system:
std_system.register_fallbacks(context)
context = context.create_child_context()
std_system.register(context, delegates)
if common:
std_common.register(context)

View File

@ -493,3 +493,13 @@ def meta(name, value):
fd.meta[name] = value
return func
return wrapper
def yaql_property(python_type):
def decorator(func):
@name('#property#{0}'.format(get_function_definition(func).name))
@parameter('obj', yaqltypes.PythonType(python_type, False))
def wrapper(obj):
return func(obj)
return wrapper
return decorator

View File

@ -302,11 +302,13 @@ class Context(HiddenParameterType, SmartType):
class Delegate(HiddenParameterType, SmartType):
def __init__(self, name=None, with_context=False, method=False):
def __init__(self, name=None, with_context=False, method=False,
use_convention=True):
super(Delegate, self).__init__(False)
self.name = name
self.with_context = with_context
self.method = method
self.use_convention = use_convention
def convert(self, value, receiver, context, function_spec, engine,
*convert_args, **convert_kwargs):
@ -331,7 +333,7 @@ class Delegate(HiddenParameterType, SmartType):
return new_context(
name, engine, new_receiver,
use_convention=True)(*args, **kwargs)
use_convention=self.use_convention)(*args, **kwargs)
func.__unwrapped__ = value
return func

View File

@ -117,6 +117,14 @@ def lambda_(func):
return func
@specs.name('#operator_.')
@specs.parameter('name', yaqltypes.Keyword())
@specs.inject('func', yaqltypes.Delegate(use_convention=False))
def get_property(func, obj, name):
func_name = '#property#{0}'.format(name)
return func(func_name, obj)
def register(context, delegates=False):
context.register_function(get_context_data)
context.register_function(op_dot)
@ -130,3 +138,7 @@ def register(context, delegates=False):
if delegates:
context.register_function(call)
context.register_function(lambda_)
def register_fallbacks(context):
context.register_function(get_property)

View File

@ -13,6 +13,7 @@
# under the License.
from yaql.language import exceptions
from yaql.language import specs
import yaql.tests
@ -141,3 +142,17 @@ class TestSystem(yaql.tests.TestCase):
self.assertEqual([4, 5, 6], self.eval(
'$.where(lambda($ > 3)())',
data=data))
def test_properties(self):
@specs.yaql_property(int)
def neg_value(value):
return -value
self.context.register_function(neg_value)
self.assertEqual(-123, self.eval('123.negValue'))
self.assertRaises(exceptions.NoMatchingFunctionException,
self.eval, '"123".negValue')
self.assertRaises(exceptions.NoMatchingFunctionException,
self.eval, 'null.negValue')
self.assertRaises(exceptions.NoFunctionRegisteredException,
self.eval, '123.neg_value')