Major 0.3 changes implemented

This commit is contained in:
ativelkov 2014-01-22 22:05:04 -08:00
parent 635c2de97d
commit 3a18057f20
30 changed files with 1447 additions and 419 deletions

View File

@ -14,28 +14,37 @@
import types
import examples.ns.definition
from yaql.functions.decorators import arg
from yaql.functions.old.decorators import arg
@arg('short_name', type=types.StringType)
def expand_namespace(short_name):
@arg('value', type=types.StringType)
def expand_property_namespace(short_name, value):
fqns = examples.ns.definition.get_fqns(short_name)
if not fqns:
raise Exception(
"Namespace with alias '{0}' is unknown".format(short_name))
else:
return fqns
@arg('fqns', type=types.StringType)
@arg('value', type=types.StringType)
def validate(fqns, value):
if not examples.ns.definition.validate(fqns, value):
raise Exception(
"Namespace '{0}' does not contain name '{1}'".format(fqns, value))
return "{0}.{1}".format(fqns, value)
@arg('short_name', type=types.StringType)
@arg('value', eval_arg=False, function_only=True)
def expand_function_namespace(short_name, value):
fqns = examples.ns.definition.get_fqns(short_name)
if not fqns:
raise Exception(
"Namespace with alias '{0}' is unknown".format(short_name))
if not examples.ns.definition.validate(fqns, value.function_name):
raise Exception(
"Namespace '{0}' does not contain name '{1}'".format(fqns, value))
value.function_name = "{0}.{1}".format(fqns, value.function_name)
return value
def register_in_context(context):
context.register_function(expand_namespace, 'operator_:')
context.register_function(validate, 'validate')
context.register_function(expand_property_namespace, 'operator_:')
context.register_function(expand_function_namespace, 'operator_:')

View File

@ -12,22 +12,39 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
from examples import testdata, ns_functions
import types
import containers
from examples.ns import definition
from examples import ns_functions
from yaql.tests import testdata
import yaql
# DEPRECATED. Use cli to run samples
context = yaql.create_context()
ns_functions.register_in_context(context)
data = testdata.data
definition.register_shortening('tst', 'com.examples.test')
definition.register_symbol('com.examples.test', 'Symbol')
definition.register_symbol('com.examples.test', 'Function')
context.register_function(testdata.process_customer, 'com.examples.test.Function')
expression_list = [
"$.services.join($.users, $1.yaql:owner=$2.id, dict('Service Name'=>$1.yaql:name,'User email'=>$2.email, 'Parent type'=>$1.yaql:parent_service))[$.'Parent type'=ex:Service0]",
"range(0, 10 * random()).select(dict(num => $)).list()",
"range(0).select(random()).takeWhile($ < 0.9).sum()",
# "$.services.join($.users, $1.yaql:owner=$2.id, dict('Service Name'=>$1.yaql:name,'User email'=>$2.email, 'Parent type'=>$1.yaql:parent_service))[$.'Parent type'=ex:Service0]",
# "range(0, 10 * random()).select(dict(num => $)).list()",
# "range(0).select(random()).takeWhile($ < 0.9).sum()",
# "$.users[0].tst:Function()"
"$.services"
]
parsed_list = [yaql.parse(expr) for expr in expression_list]
results = [expr.evaluate(data, context) for expr in parsed_list]
@ -35,7 +52,7 @@ i = 1
for res in results:
print "result #{0}".format(i)
i += 1
if isinstance(res, collections.Iterable):
if isinstance(res, containers.Iterable) and not isinstance(res, types.StringType):
for r in res:
print r
else:

View File

@ -12,9 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import parser
import context
from yaql.functions import builtin, extended
import functions
from yaql.language import parser, context
__versioninfo__ = (0, 3, 0)
__version__ = '.'.join(map(str, __versioninfo__))
@ -24,9 +23,8 @@ def parse(expression):
return parser.parse(expression)
def create_context(include_extended_functions=True):
def create_context(register_functions=True):
cont = context.Context()
builtin.add_to_context(cont)
if include_extended_functions:
extended.add_to_context(cont)
if register_functions:
functions.register(cont)
return context.Context(cont)

View File

@ -16,15 +16,16 @@ import json
import os
import re
import types
import yaql
import readline
from json import JSONDecoder
from yaql.context import Context
from yaql.exceptions import YaqlParsingException, YaqlException
from yaql.functions.decorators import arg, ContextAware
from yaql.lexer import lexer
from yaql.utils import limit
from yaql.exceptions import YaqlParsingException
import yaql
from yaql.functions.old.decorators import arg, ContextAware
from yaql.language import lexer
from yaql.language.utils import limit
PROMPT = "yaql> "

View File

@ -11,3 +11,14 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import system
import strings
import containers
from yaql.functions import arithmetic
def register(context):
system.add_to_context(context)
strings.add_to_context(context)
containers.add_to_context(context)
arithmetic.add_to_context(context)

View File

@ -0,0 +1,95 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from yaql.language.engine import parameter
def _is_a_number(value):
return isinstance(value, (int, long, float, complex))
@parameter('value', custom_validator=_is_a_number)
def unary_minus(value):
return -1 * value
@parameter('value', custom_validator=_is_a_number)
def unary_plus(value):
return value
@parameter('a', custom_validator=_is_a_number)
@parameter('b', custom_validator=_is_a_number)
def plus(a, b):
return a + b
@parameter('a', custom_validator=_is_a_number)
@parameter('b', custom_validator=_is_a_number)
def minus(a, b):
return a - b
@parameter('a', custom_validator=_is_a_number)
@parameter('b', custom_validator=_is_a_number)
def multiply(a, b):
return a * b
@parameter('a', custom_validator=_is_a_number)
@parameter('b', custom_validator=_is_a_number)
def divide(a, b):
return a / b
# comparison
def less_then(a, b):
return a < b
def greater_or_equals(a, b):
return a >= b
def less_or_equals(a, b):
return a <= b
def greater_then(a, b):
return a > b
def equals(a, b):
return a == b
def not_equals(a, b):
return a != b
def add_to_context(context):
# prefix unary
context.register_function(unary_minus, 'unary_-')
context.register_function(unary_plus, 'unary_+')
# arithmetic actions
context.register_function(plus, 'operator_+')
context.register_function(minus, 'operator_-')
context.register_function(multiply, 'operator_*')
context.register_function(divide, 'operator_/')
# comparison
context.register_function(greater_then, 'operator_>')
context.register_function(less_then, 'operator_<')
context.register_function(greater_or_equals, 'operator_>=')
context.register_function(less_or_equals, 'operator_<=')
context.register_function(equals, 'operator_=')
context.register_function(not_equals, 'operator_!=')

View File

@ -1,258 +0,0 @@
# Copyright (c) 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import types
from yaql.exceptions import YaqlExecutionException
from yaql.expressions import Constant, Function
from yaql.functions.decorators import arg, ContextAware, argument, context_aware
from yaql.utils import limit
# basic language operations:
# retrieving data from context, attribution and wrapping in parenthesis
@ContextAware()
def get_context_data(context, path):
return context.get_data(path())
@arg('self', type=collections.Iterable,
custom_validator=lambda v: not isinstance(v, types.DictionaryType))
@arg('att_name', constant_only=True)
def collection_attribution(self, att_name):
def get_att_or_key(item):
value = att_name
if hasattr(item, value):
return getattr(item, value)
if isinstance(item, types.DictionaryType):
return item.get(value)
return None
for item in self:
val = get_att_or_key(item)
if isinstance(val, collections.Iterable) and \
not isinstance(val, types.StringTypes):
for v in val:
yield v
else:
yield val
@arg('self', type=types.DictionaryType)
@arg('att_name', constant_only=True)
def dict_attribution(self, att_name):
return self.get(att_name)
@arg('att_name', constant_only=True)
def obj_attribution(self, att_name):
return getattr(self(), att_name, None)
@arg('method', eval_arg=False, function_only=True)
def method_call(self, method):
return method(self)
@context_aware
def testtt(context):
pass
def wrap(value):
return value()
# Collection filtering
@arg("index", types.IntType)
def get_by_index(this, index):
this = this()
if isinstance(this, types.GeneratorType):
this = list(this)
return this[index]
def filter_by_predicate(self, predicate):
for item in self():
r = predicate(item)
if r:
yield item
# comparison operations
def less_then(a, b):
return a() < b()
def greater_or_equals(a, b):
return a() >= b()
def less_or_equals(a, b):
return a() <= b()
def greater_then(a, b):
return a() > b()
def equals(a, b):
return a() == b()
def not_equals(a, b):
return a() != b()
def is_in(a, b):
return a() in b()
# arithmetic actions
def plus(a, b):
return a() + b()
def minus(a, b):
return a() - b()
def multiply(a, b):
return a() * b()
def divide(a, b):
return a() / b()
# Boolean operations
# @EvalArg('a', arg_type=types.BooleanType)
# @EvalArg('b', arg_type=types.BooleanType)
def _and(a, b):
return a() and b()
# @EvalArg('a', arg_type=types.BooleanType)
# @EvalArg('b', arg_type=types.BooleanType)
def _or(a, b):
return a() or b()
# @EvalArg('self', arg_type=types.BooleanType)
def _not(self):
return not self()
#data structure creations
def build_tuple(left, right):
_list = []
if left.key == 'operator_=>':
_list.extend(left())
else:
_list.append(left())
_list.append(right())
return tuple(_list)
def build_list(*args):
res = []
for arg in args:
arg = arg()
if isinstance(arg, types.GeneratorType):
arg = limit(arg)
res.append(arg)
return res
def build_dict(*tuples):
res = {}
for t in tuples:
tt = t()
res[tt[0]] = tt[1]
return res
# type conversions
def to_int(value):
return int(value())
def to_float(value):
return float(value())
@arg('value')
def to_bool(value):
if isinstance(value, types.StringTypes):
if value.lower() == 'false':
return False
return bool(value)
def add_to_context(context):
context.register_function(testtt, 'test')
# basic language operations:
# retrieving data from context, attribution and wrapping in parenthesis
context.register_function(get_context_data, 'get_context_data')
context.register_function(collection_attribution, 'operator_.')
context.register_function(dict_attribution, 'operator_.')
context.register_function(obj_attribution, 'operator_.')
context.register_function(method_call, 'operator_.')
context.register_function(wrap, 'wrap')
# collection filtering
context.register_function(get_by_index, "where")
context.register_function(filter_by_predicate, "where")
# comparison operations
context.register_function(greater_then, 'operator_>')
context.register_function(less_then, 'operator_<')
context.register_function(greater_or_equals, 'operator_>=')
context.register_function(less_or_equals, 'operator_<=')
context.register_function(equals, 'operator_=')
context.register_function(not_equals, 'operator_!=')
context.register_function(is_in, 'operator_in')
# arithmetic actions
context.register_function(plus, 'operator_+')
context.register_function(minus, 'operator_-')
context.register_function(multiply, 'operator_*')
context.register_function(divide, 'operator_/')
# Boolean operations
context.register_function(_and, 'operator_and')
context.register_function(_or, 'operator_or')
context.register_function(_not, 'operator_not')
#data structure creations
context.register_function(build_list, 'list')
context.register_function(build_dict, 'dict')
context.register_function(build_tuple, 'operator_=>')
context.register_function(build_tuple, 'operator_=>:')
#stubs for namespace resolving
context.register_function(lambda a, b: a() + "." + b(), 'validate')
context.register_function(lambda a: a(), 'operator_:')
#type conversions
context.register_function(to_bool, 'bool')
context.register_function(to_int, 'int')
context.register_function(to_float, 'float')

View File

@ -0,0 +1,93 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import types
from yaql.language.exceptions import YaqlExecutionException
from yaql.language.engine import parameter
from yaql.language.utils import limit
@parameter("index", arg_type=types.IntType)
def get_by_index(data, index):
if isinstance(data, types.GeneratorType):
data = list(data)
return data[index]
@parameter("self", arg_type=collections.Iterable,
custom_validator=lambda v: not isinstance(v, types.StringTypes))
@parameter("predicate", function_only=True, lazy=True)
def filter_by_predicate(self, predicate):
for item in self:
r = predicate(item)
if not isinstance(r, types.BooleanType):
raise YaqlExecutionException("Not a predicate")
if r is True:
yield item
def build_list(*args):
res = []
for arg in args:
if isinstance(arg, types.GeneratorType):
arg = limit(arg)
res.append(arg)
return res
@parameter("b", arg_type=collections.Iterable,
custom_validator=lambda v: not isinstance(v, types.StringTypes))
def is_in(a, b):
return a in b
@parameter('self', arg_type=collections.Iterable,
custom_validator=lambda v: not isinstance(v, types.DictionaryType))
@parameter('att_name', constant_only=True)
def collection_attribution(self, att_name):
def get_att_or_key(col_item):
value = att_name
if isinstance(col_item, types.DictionaryType):
return col_item.get(value)
else:
return getattr(col_item, value)
for item in self:
val = get_att_or_key(item)
yield val
@parameter('arg1', lazy=True,
custom_validator=lambda v: v.key != 'operator_=>')
def build_new_tuple(arg1, arg2):
return arg1(), arg2
@parameter('arg1', lazy=True,
custom_validator=lambda v: v.key == 'operator_=>')
def append_tuple(arg1, arg2):
res = []
for tup in arg1():
res.append(tup)
res.append(arg2)
return tuple(res)
def add_to_context(context):
context.register_function(get_by_index, 'where')
context.register_function(filter_by_predicate, 'where')
context.register_function(build_list, 'list')
context.register_function(is_in, 'operator_in')
context.register_function(collection_attribution, 'operator_.')
context.register_function(build_new_tuple, 'operator_=>')
context.register_function(append_tuple, 'operator_=>')

View File

@ -0,0 +1 @@
__author__ = 'ativelkov'

View File

@ -0,0 +1,173 @@
# Copyright (c) 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import types
import containers
from yaql.language.engine import context_aware
from yaql.functions.old.decorators import arg
from yaql.language.utils import limit
def is_in(a, b):
return a in b
def add_to_context(context):
#context actions
context.register_function(get_context_data, 'get_context_data')
#string operations
context.register_function(string_concatination, 'operator_+')
# ======== old
# basic language operations:
# retrieving data from context, attribution and wrapping in parenthesis
# @ContextAware()
@context_aware
def testtt(context):
pass
def wrap(value):
return value()
# Boolean operations
# @EvalArg('a', arg_type=types.BooleanType)
# @EvalArg('b', arg_type=types.BooleanType)
def _and(a, b):
return a() and b()
# @EvalArg('a', arg_type=types.BooleanType)
# @EvalArg('b', arg_type=types.BooleanType)
def _or(a, b):
return a() or b()
# @EvalArg('self', arg_type=types.BooleanType)
def _not(self):
return not self()
#data structure creations
def build_tuple(left, right):
_list = []
if left.key == 'operator_=>':
_list.extend(left())
else:
_list.append(left())
_list.append(right())
return tuple(_list)
def build_list(*args):
res = []
for arg in args:
arg = arg()
if isinstance(arg, types.GeneratorType):
arg = limit(arg)
res.append(arg)
return res
def build_dict(*tuples):
res = {}
for t in tuples:
tt = t()
res[tt[0]] = tt[1]
return res
# type conversions
def to_int(value):
return int(value())
def to_float(value):
return float(value())
@arg('value')
def to_bool(value):
if isinstance(value, types.StringTypes):
if value.lower() == 'false':
return False
return bool(value)
def add_to_context_old(context):
context.register_function(testtt, 'test')
# basic language operations:
# retrieving data from context, attribution and wrapping in parenthesis
# context.register_function(get_context_data, 'get_context_data')
# context.register_function(collection_attribution, 'operator_.')
# context.register_function(dict_attribution, 'operator_.')
# context.register_function(obj_attribution, 'operator_.')
# context.register_function(method_call, 'operator_.')
# context.register_function(wrap, 'wrap')
# collection filtering
# context.register_function(get_by_index, "where")
# context.register_function(filter_by_predicate, "where")
# arithmetic actions
# context.register_function(plus, 'operator_+')
# context.register_function(minus, 'operator_-')
# context.register_function(multiply, 'operator_*')
# context.register_function(divide, 'operator_/')
# Boolean operations
context.register_function(_and, 'operator_and')
context.register_function(_or, 'operator_or')
context.register_function(_not, 'operator_not')
#data structure creations
context.register_function(build_list, 'list')
context.register_function(build_dict, 'dict')
context.register_function(build_tuple, 'operator_=>')
context.register_function(build_tuple, 'operator_=>:')
#stubs for namespace resolving
context.register_function(lambda a, b: a() + "." + b(), 'validate')
context.register_function(lambda a: a(), 'operator_:')
#type conversions
context.register_function(to_bool, 'bool')
context.register_function(to_int, 'int')
context.register_function(to_float, 'float')

View File

@ -14,13 +14,13 @@
from functools import wraps
import inspect
import types
from yaql.exceptions import NoArgumentFound
from yaql.exceptions import DuplicateArgumentDecoratorException
from yaql.exceptions import YaqlExecutionException
from yaql.exceptions import DuplicateContextDecoratorException
from yaql.expressions import Expression
from yaql.expressions import Constant
from yaql.expressions import Function
from yaql.language.exceptions import NoParameterFoundException
from yaql.language.exceptions import DuplicateParameterDecoratorException
from yaql.language.exceptions import YaqlExecutionException
from yaql.language.exceptions import DuplicateContextDecoratorException
from yaql.language.expressions import Expression
from yaql.language.expressions import Constant
from yaql.language.expressions import Function
class ArgDef(object):
@ -100,7 +100,7 @@ def _attach_arg_data(func, arg):
if not hasattr(func, 'func_arg_validate'):
func.func_arg_validate = _validate_func
if arg.arg_name in func.func_arg_rules:
raise DuplicateArgumentDecoratorException(func.__name__, arg.arg_name)
raise DuplicateParameterDecoratorException(func.__name__, arg.arg_name)
if arg.is_context:
if hasattr(func, 'func_is_context_aware') and \
@ -131,11 +131,11 @@ def argument(arg_name,
return get_wrapper
def context_aware(arg=None):
if callable(arg): # no-arg decorator case
return _attach_arg_data(arg, ArgDef('context', is_context=True))
else:
return argument(arg, is_context=True)
# def context_aware(arg=None):
# if callable(arg): # no-arg decorator case
# return _attach_arg_data(arg, ArgDef('context', is_context=True))
# else:
# return argument(arg, is_context=True)
class arg(object):
@ -154,7 +154,7 @@ class arg(object):
else:
real_args = inspect.getargspec(function).args
if not self.arg_name in real_args:
raise NoArgumentFound(function.__name__,
raise NoParameterFoundException(function.__name__,
self.arg_name)
if not hasattr(function, 'arg_requirements'):
function.arg_requirements = {self.arg_name: self}
@ -184,7 +184,7 @@ class ContextAware(object):
def context_aware_function(context, *args):
real_args = inspect.getargspec(function).args
if not self.context_parameter_name in real_args:
raise NoArgumentFound(function.__name__,
raise NoParameterFoundException(function.__name__,
self.context_parameter_name)
index = real_args.index(self.context_parameter_name)
args_to_pass = list(args)

View File

@ -11,14 +11,16 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import random
import types
import itertools
from yaql.exceptions import YaqlExecutionException
from yaql.functions.decorators import arg, ContextAware
from yaql.utils import limit
import containers
from yaql.functions.old.decorators import arg, ContextAware
from yaql.language.utils import limit
def join(self, others, join_predicate, composer):
@ -123,6 +125,10 @@ def switch(self, *conditions):
def add_to_context(context):
pass
def add_to_context_old(context):
context.register_function(join, 'join')
context.register_function(select, 'select')
context.register_function(_sum, 'sum')

30
yaql/functions/strings.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import types
from yaql.language.engine import parameter
@parameter('a', arg_type=types.StringTypes)
@parameter('b', arg_type=types.StringTypes)
def string_concatenation(a, b):
return a + b
@parameter('self', arg_type=types.StringTypes, is_self=True)
def as_list(self):
return list(self)
def add_to_context(context):
context.register_function(string_concatenation, 'operator_+')
context.register_function(as_list, 'asList')

64
yaql/functions/system.py Normal file
View File

@ -0,0 +1,64 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import types
from yaql.language.exceptions import YaqlExecutionException
from yaql.language.engine import parameter, context_aware, inverse_context
# This unit defines basic YAQL functions, such as
# context retrieval, object property retrieval, method calls etc
def _is_object(value):
return not isinstance(value, (
types.DictionaryType, collections.Iterable)) \
or isinstance(value, types.StringType)
@context_aware
def get_context_data(context, path):
return context.get_data(path)
@parameter('att_name', constant_only=True)
@parameter('self', custom_validator=_is_object)
def obj_attribution(self, att_name):
try:
return getattr(self, att_name)
except AttributeError:
raise YaqlExecutionException("Unable to retrieve object attribute")
@parameter('self', arg_type=types.DictionaryType)
@parameter('att_name', constant_only=True)
def dict_attribution(self, att_name):
return self.get(att_name)
@parameter('method', lazy=True, function_only=True)
@parameter('self')
@inverse_context
def method_call(self, method):
return method(sender=self)
def add_to_context(context):
context.register_function(get_context_data)
context.register_function(obj_attribution, 'operator_.')
context.register_function(dict_attribution, 'operator_.')
context.register_function(method_call, 'operator_.')
context.register_function(lambda val: val, 'wrap')

13
yaql/language/__init__.py Normal file
View File

@ -0,0 +1,13 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import inspect
import yaql.language
class Context():
@ -20,6 +20,7 @@ class Context():
self.parent_context = parent_context
self.functions = {}
self.data = {}
if data:
self.data['$'] = data
if parent_context:
@ -27,32 +28,37 @@ class Context():
else:
self.depth = 0
def register_function(self, function, name):
if hasattr(function, "is_context_aware"):
num_params = function.context_aware.get_num_callable_args()
if function.context_aware.varargs:
num_params = 0
else:
argspec = inspect.getargspec(function)
num_params = len(argspec.args)
if argspec.varargs:
num_params = 0
if not num_params:
num_params = '*'
def take_snapshot(self):
return {
'functions': self.functions.copy(),
'data': self.data.copy()
}
def restore(self, snapshot):
self.data = snapshot['data'].copy()
self.functions = snapshot['functions'].copy()
def register_function(self, function, name=None):
func_def = yaql.language.engine.yaql_function(function)
func_def.build()
num_params = func_def.get_num_params()
if not name:
name = func_def.function.func_name
if not name in self.functions:
self.functions[name] = {}
if not num_params in self.functions[name]:
self.functions[name][num_params] = [function]
self.functions[name][num_params] = [func_def]
else:
self.functions[name][num_params].append(function)
self.functions[name][num_params].append(func_def)
def get_functions(self, function_name, num_params):
result = []
if function_name in self.functions:
if num_params in self.functions[function_name]:
result += self.functions[function_name][num_params]
if '*' in self.functions[function_name]:
result += self.functions[function_name]['*']
if -1 in self.functions[function_name]:
result += self.functions[function_name][-1]
if self.parent_context:
result += self.parent_context.get_functions(function_name,
@ -66,9 +72,8 @@ class Context():
if path == '$':
self.data['$1'] = data
def get_data(self, path='$'):
if path in self.data:
return self.data[path]
if self.parent_context:
return self.parent_context.get_data(path)
return self.parent_context.get_data(path)

243
yaql/language/engine.py Normal file
View File

@ -0,0 +1,243 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import inspect
import types
from yaql.language.exceptions import *
import yaql.language.context
import yaql.language.expressions
def yaql_function(function):
if not hasattr(function, "__yaql__"):
function.__yaql__ = YaqlFunctionDefinition(function)
return function.__yaql__
class YaqlFunctionDefinition(object):
def __init__(self, function):
self.function = function
self.is_context_aware = False
self.context_param_name = None
self.self_param_name = None
self.context_owner_param_name = None
self.param_definitions = {}
self._arg_spec = inspect.getargspec(function)
self._inverse_context = False
if self._arg_spec.keywords:
raise YaqlException("Keyword parameters are not supported")
def register_param_constraint(self, param):
if param.name not in self._arg_spec.args:
raise NoParameterFoundException(
function_name=self.function.func_name,
param_name=param.name)
if param.name in self.param_definitions:
raise DuplicateParameterDecoratorException(
function_name=self.function.func_name,
param_name=param.name)
if self.is_context_aware and param.is_context:
raise DuplicateContextDecoratorException(
function_name=self.function.func_name)
if self.context_owner_param_name and param.own_context:
raise DuplicateContextOwnerDecoratorException(
function_name=self.function.func_name)
self.param_definitions[param.name] = param
if param.is_context:
self.is_context_aware = True
self.context_param_name = param.name
if param.is_self is None:
param.is_self = self._arg_spec.args.index(
param.name) == 0 and param.name == 'self'
if param.is_self:
self.self_param_name = param.name
if param.lazy:
raise YaqlException("Self parameter cannot be lazy")
def get_num_params(self):
if self._arg_spec.varargs or self._arg_spec.keywords:
return -1
if self.is_context_aware:
return len(self._arg_spec.args) - 1
else:
return len(self._arg_spec.args)
def get_context_owner_index(self):
for param in self.param_definitions.values():
if param.inverse_context:
return param.name
return None
def build(self):
for arg_name in self._arg_spec.args:
if arg_name not in self.param_definitions:
self.register_param_constraint(ParameterDefinition(arg_name))
def inverse_context(self):
self._inverse_context = True
def __repr__(self):
return self.function.func_name + "_" + str(self.get_num_params())
def __call__(self, context, sender, *args):
if sender and not self.self_param_name:
raise YaqlExecutionException(
"The function cannot be run as a method")
num_args = len(args) + 1 if sender else len(args)
if 0 <= self.get_num_params() != num_args:
raise YaqlExecutionException(
"Expected {0} args, got {1}".format(self.get_num_params(),
len(args)))
input_position = 0
prepared_list = []
if self._inverse_context:
context_to_pass = context
else:
context_to_pass = yaql.language.context.Context(context)
if self.get_num_params() >= 0:
for arg_name in self._arg_spec.args:
definition = self.param_definitions[arg_name]
if sender and definition.is_self:
prepared_list.append(sender)
elif definition.is_context:
prepared_list.append(context)
else:
arg = args[input_position]
input_position += 1
value, base_context = definition.validate(
arg.create_callable(context_to_pass))
prepared_list.append(value)
if self._inverse_context:
context_to_pass = yaql.language.context.Context(base_context)
else:
context_to_pass = yaql.language.context.Context(context)
else:
for arg in args:
c = arg.create_callable(context_to_pass)
val = c()
base_context = c.yaql_context
prepared_list.append(val)
if self._inverse_context:
context_to_pass = yaql.language.context.Context(base_context)
else:
context_to_pass = yaql.language.context.Context(context)
if self._inverse_context:
final_context = context_to_pass
else:
final_context = context
return self.function(*prepared_list), final_context
class ParameterDefinition(object):
def __init__(self,
name,
lazy=False,
arg_type=None,
custom_validator=None,
constant_only=False,
function_only=False,
is_context=False,
is_self=None):
self.arg_type = arg_type
self.name = name
self.lazy = lazy
self.arg_type = arg_type
self.custom_validator = custom_validator
self.constant_only = constant_only
self.function_only = function_only
self.is_context = is_context
self.is_self = is_self
def validate(self, value):
if self.constant_only:
if not isinstance(value, yaql.language.expressions.Constant.Callable):
raise YaqlExecutionException(
"Parameter {0} has to be a constant".format(self.name))
if self.function_only:
if not isinstance(value, yaql.language.expressions.Function.Callable):
raise YaqlExecutionException(
"Parameter {0} has to be a function".format(self.name))
if not self.lazy:
try:
res = value()
except:
raise YaqlExecutionException(
"Unable to evaluate parameter {0}".format(self.name))
else:
res = value
context = value.yaql_context
if self.arg_type:
# we need a special handling for booleans, as
# isinstance(boolean_value, integer_type)
# will return true, which is not what we expect
if type(res) is types.BooleanType:
if self.arg_type is not types.BooleanType:
raise YaqlExecutionException(
"Type of the parameter is not boolean")
elif not isinstance(res, self.arg_type):
raise YaqlExecutionException(
"Type of the parameter is not {0}".format(
str(self.arg_type)))
if self.custom_validator:
if not self.custom_validator(res):
raise YaqlExecutionException(
"Parameter didn't pass the custom validation")
return res, context
def parameter(name,
lazy=False,
arg_type=None,
custom_validator=None,
constant_only=False,
function_only=False,
is_context=False,
is_self=None):
def get_wrapper(func):
param = ParameterDefinition(name,
lazy,
arg_type,
custom_validator,
constant_only,
function_only,
is_context,
is_self)
yaql_function(func).register_param_constraint(param)
return func
return get_wrapper
def inverse_context(func):
yaql_function(func).inverse_context()
return func
def context_aware(arg):
if callable(arg): # no-arg decorator case, arg is a decorated function
yaql_function(arg).register_param_constraint(
ParameterDefinition('context', is_context=True))
return arg
else: # decorator is called with args, arg is the name of parameter
return parameter(arg, is_context=True)

View File

@ -32,26 +32,32 @@ class YaqlExecutionException(YaqlException):
pass
class DuplicateArgumentDecoratorException(YaqlException):
def __init__(self, function_name, argument_name):
message = "Function '{0}' has multiple decorators for argument '{1}'".\
format(function_name, argument_name)
super(DuplicateArgumentDecoratorException, self).__init__(message)
class DuplicateParameterDecoratorException(YaqlException):
def __init__(self, function_name, param_name):
message = "Function '{0}' has multiple decorators for parameter '{1}'". \
format(function_name, param_name)
super(DuplicateParameterDecoratorException, self).__init__(message)
class DuplicateContextDecoratorException(YaqlException):
def __init__(self, function_name):
message = "Function '{0}' has multiple context-arg decorators".\
message = "Function '{0}' has multiple context-param decorators". \
format(function_name)
super(DuplicateContextDecoratorException, self).__init__(message)
class NoArgumentFound(YaqlException):
def __init__(self, function_name, argument_name):
message = \
"Function '{0}' has no argument called '{1}'". \
format(function_name, argument_name)
super(NoArgumentFound, self).__init__(message)
class DuplicateContextOwnerDecoratorException(YaqlException):
def __init__(self, function_name):
message = "Function '{0}' has multiple context-owner decorators". \
format(function_name)
super(DuplicateContextOwnerDecoratorException, self).__init__(message)
class NoParameterFoundException(YaqlException):
def __init__(self, function_name, param_name):
message = "Function '{0}' has no parameter called '{1}'". \
format(function_name, param_name)
super(NoParameterFoundException, self).__init__(message)
class YaqlParsingException(YaqlException):

View File

@ -11,10 +11,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import inspect
import types
from context import *
from exceptions import YaqlExecutionException, NoFunctionRegisteredException
from yaql.language.context import Context
from yaql.language.exceptions import (YaqlExecutionException,
NoFunctionRegisteredException,
YaqlException)
import yaql
@ -54,49 +57,36 @@ class Function(Expression):
self.function_name = function_name
self.args = args
self.yaql_context = context
self.obj_wrapper = None
def __call__(self, *f_params):
args_to_pass = []
if f_params:
if len(f_params) == 1 and isinstance(f_params[0],
Expression.Callable):
self.obj_wrapper = f_params[0]
self.yaql_context = Context(self.obj_wrapper.yaql_context)
else:
param_context = self._find_param_context()
param_context.set_data(f_params[0])
for i in range(0, len(f_params)):
param_context.set_data(f_params[i], '$' + str(i + 1))
if self.obj_wrapper:
args_to_pass.append(self.obj_wrapper)
for arg in self.args:
argContext = Context(self.yaql_context)
wrapped_arg = arg.create_callable(argContext)
args_to_pass.append(wrapped_arg)
def __call__(self, *context_args, **context_kwargs):
sender = context_kwargs.get('sender')
numArg = len(args_to_pass)
fs = self.yaql_context.get_functions(self.function_name, numArg)
if context_args: # passed args have to be placed in the context
self.yaql_context.set_data(context_args[0])
for i, param in enumerate(context_args):
self.yaql_context.set_data(param, '$' + str(i + 1))
for arg_name, arg_value in context_kwargs.items():
self.yaql_context.set_data(arg_value, '$' + arg_name)
num_args = len(self.args) + 1 if sender else len(self.args)
fs = self.yaql_context.get_functions(self.function_name, num_args)
if not fs:
raise NoFunctionRegisteredException(self.function_name, numArg)
raise NoFunctionRegisteredException(self.function_name,
num_args)
snapshot = self.yaql_context.take_snapshot()
for func in fs:
try:
processed_args = pre_process_args(func, args_to_pass)
if hasattr(func, "is_context_aware"):
return func(self.yaql_context, *processed_args)
else:
return func(*processed_args)
result, res_context = func(self.yaql_context, sender,
*self.args)
self.yaql_context = res_context
return result
except YaqlExecutionException:
self.yaql_context.restore(snapshot)
continue
raise YaqlExecutionException("Unable to run " + self.function_name)
def _find_param_context(self):
context = self.yaql_context
wrapper = self.obj_wrapper
while wrapper:
context = wrapper.yaql_context
wrapper = getattr(wrapper, 'obj_wrapper', None)
return context
raise YaqlException(
"Registered function(s) matched but none"
" could run successfully")
def create_callable(self, context):
return Function.Callable(self, context, self.name, self.args)
@ -110,7 +100,7 @@ class BinaryOperator(Function):
class UnaryOperator(Function):
def __init__(self, op, obj):
super(UnaryOperator, self).__init__("operator_" + op, obj)
super(UnaryOperator, self).__init__("unary_" + op, obj)
class Filter(Function):
@ -155,8 +145,9 @@ class Constant(Expression):
return str(self.value)
class Callable(Expression.Callable):
def __init__(self, wrapped, value):
super(Constant.Callable, self).__init__(wrapped, None, key=value)
def __init__(self, wrapped, value, context):
super(Constant.Callable, self).__init__(wrapped, context,
key=value)
self.value = value
# noinspection PyUnusedLocal
@ -164,7 +155,7 @@ class Constant(Expression):
return self.value
def create_callable(self, context):
return Constant.Callable(self, self.value)
return Constant.Callable(self, self.value, context)
def pre_process_args(func, args):

View File

@ -13,7 +13,7 @@
# under the License.
import ply.lex as lex
from yaql.exceptions import YaqlLexicalException
from yaql.language.exceptions import YaqlLexicalException
keywords = {
'true': 'TRUE',
@ -29,6 +29,14 @@ keywords_to_val = {
right_associative = [':']
unary_prefix = {
'-': "UNARY_MINUS",
'+': "UNARY_PLUS",
'~': "UNARY_TILDE",
'!': "UNARY_NOT"
}
op_to_level = {
'abc': 0,
'|' : 1,
@ -38,12 +46,12 @@ op_to_level = {
'>' : 4,
'=' : 5,
'!' : 5,
':' : 6,
'+' : 7,
'-' : 7,
'*' : 8,
'/' : 8,
'%' : 8
'+' : 6,
'-' : 6,
'*' : 7,
'/' : 7,
'%' : 7,
'.' : 8
}
ops = {
@ -78,8 +86,8 @@ tokens = [
'FILTER',
# 'TUPLE',
'NOT',
'DOLLAR',
] + list(keywords.values())+list(ops.values())
'DOLLAR'
] + list(keywords.values())+list(ops.values()) + list(unary_prefix.values())
literals = "()],"
@ -150,12 +158,18 @@ def t_CHAR_ORB(t):
"""
[!@#%^&*=.:;`~\\-><+/]+
"""
t.type = get_orb_op_type(t.value[0], t.value[-1])
if t.value in unary_prefix:
t.type = unary_prefix[t.value]
else:
t.type = get_orb_op_type(t.value[0], t.value[-1])
return t
def get_orb_op_type(first_char, last_char):
if first_char.isalpha() or first_char == '_':
level = op_to_level['abc']
else:

View File

@ -13,12 +13,12 @@
# under the License.
import types
import ply.yacc as yacc
import expressions
import exceptions
import lexer
import tempfile
import ply.yacc as yacc
from yaql.language import lexer, expressions, exceptions
tokens = lexer.tokens
@ -95,7 +95,7 @@ def p_function_no_args(p):
"""
func : FUNC ')'
"""
p[0] = expressions.Function(p[1], None)
p[0] = expressions.Function(p[1])
def p_function_w_args(p):
@ -132,12 +132,15 @@ def p_binary(p):
| value LVL8_RIGHT value
| value LVL9_LEFT value
| value LVL9_RIGHT value
| value UNARY_PLUS value
| value UNARY_MINUS value
| value UNARY_NOT value
| value UNARY_TILDE value
"""
p[0] = expressions.BinaryOperator(p[2], p[1], p[3])
def p_val_with_unary_op(p):
"""
value : NOT value
@ -145,6 +148,16 @@ def p_val_with_unary_op(p):
p[0] = expressions.UnaryOperator(p[1], p[2])
def p_unary_prefix(p):
"""
value : UNARY_TILDE value
| UNARY_PLUS value
| UNARY_NOT value
| UNARY_MINUS value
"""
p[0] = expressions.UnaryOperator(p[1], p[2])
def p_val_in_parenthesis(p):
"""
value : '(' value ')'
@ -184,21 +197,21 @@ precedence = (
('right', lexer.ops[(3, 'r')]),
('left', lexer.ops[(4, 'l')]),
('right', lexer.ops[(4, 'r')]),
('left', lexer.ops[(5, 'l', )], 'NOT'),
('left', lexer.ops[(5, 'l', )], 'NOT', 'UNARY_NOT'),
('right', lexer.ops[(5, 'r')]),
('left', lexer.ops[(6, 'l')]),
('left', lexer.ops[(6, 'l')], 'UNARY_PLUS', 'UNARY_MINUS'),
('right', lexer.ops[(6, 'r')]),
('left', lexer.ops[(7, 'l')]),
('right', lexer.ops[(7, 'r')]),
('left', lexer.ops[(8, 'l')]),
('right', lexer.ops[(8, 'r')]),
('left', lexer.ops[(9, 'l')]),
('left', lexer.ops[(9, 'l')], 'UNARY_TILDE'),
('right', lexer.ops[(9, 'r')]),
)
# parser = yacc.yacc(debug=False, outputdir=tempfile.gettempdir(), tabmodule='parser_table')
parser = yacc.yacc()
parser = yacc.yacc(debug=False, outputdir=tempfile.gettempdir(), tabmodule='parser_table')
# parser = yacc.yacc()
def parse(expression):

View File

@ -11,7 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from yaql.exceptions import YaqlSequenceException
from yaql.language.exceptions import YaqlSequenceException
MAX_GENERATOR_ITEMS = 100000

30
yaql/tests/__init__.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (c) 2013 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import types
import unittest
import yaql
from yaql.language.utils import limit
class YaqlTest(unittest.TestCase):
def setUp(self):
self.context = yaql.create_context()
def eval(self, expression, data=None, context=None):
res = yaql.parse(expression).evaluate(data=data,
context=context or self.context)
if isinstance(res, types.GeneratorType):
return limit(res)
else:
return res

View File

@ -0,0 +1,54 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import unittest
from yaql.tests import YaqlTest
class TestSystemFunctions(YaqlTest):
def test_string_constant(self):
self.assertEquals("abc", self.eval("abc"))
self.assertEquals("100", self.eval("'100'"))
self.assertEquals('some mw const', self.eval("'some mw const'"))
def test_numeric_constant(self):
self.assertEquals(0, self.eval('0'))
self.assertEquals(100, self.eval('100'))
self.assertEquals(0, self.eval("0"))
self.assertEquals(100, self.eval("100"))
def test_negative_constant(self):
self.assertEquals(-10, self.eval('-10'))
def test_int_arithmetic(self):
self.assertEquals(20, self.eval('15+5'))
self.assertEquals(20, self.eval('15+10-5'))
self.assertEquals(20, self.eval('15+10-5*2+10/2'))
self.assertEquals(2, self.eval('5/2'))
self.assertEquals(3, self.eval('6/2'))
def test_float_arithmetic(self):
self.assertEquals(10.0, self.eval('5.0 * 2'))
self.assertEquals(10.0, self.eval('5 * 2.0'))
self.assertEquals(2.5, self.eval('5/2.0'))
self.assertEquals(2.5, self.eval('5.0/2'))
def test_mix_binary_unary(self):
self.assertEquals(15, self.eval('20 + -5'))
self.assertEquals(-25, self.eval('-20 + -5'))
self.assertEquals(-25, self.eval('-20 - +5'))
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,67 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from yaql.language.exceptions import YaqlException
from yaql.tests import YaqlTest
import yaql.tests.testdata
class TestCollections(YaqlTest):
def test_get_by_index(self):
int_list = [1, 2, 3, 4, 5, 6]
self.assertEquals(4, self.eval('$[3]', int_list))
def test_where_by_index(self):
int_list = [1, 2, 3, 4, 5, 6]
self.assertRaises(YaqlException, self.eval, '$.where(3)', int_list)
def test_filter_by_predicate(self):
int_list = [1, 2, 3, 4, 5, 6]
self.assertEquals([4, 5, 6], list(self.eval('$[$>3]', int_list)))
def test_filter_by_non_boolean_predicate(self):
int_list = [1, 2, 3, 4, 5, 6]
self.assertRaises(YaqlException, self.eval, '$.where($+1)', int_list)
def test_list_definition(self):
self.assertEquals([1, 2, 3], self.eval('list(1,2,3)'))
def test_in(self):
int_list = [1, 2, 3, 4, 5, 6]
self.assertTrue(self.eval('4 in $', int_list))
def test_not_in(self):
int_list = [1, 2, 3, 4, 5, 6]
self.assertFalse(self.eval('7 in $', int_list))
def test_iterable_property_attribution(self):
data = yaql.tests.testdata.users
expression = "$.email"
self.assertEquals(
['user1@example.com', 'user2@example.com', 'user3@example.com'],
self.eval(expression, data))
def test_iterable_property_attribution_2(self):
data = yaql.tests.testdata.data
expression = "$.users.email"
self.assertEquals(
['user1@example.com', 'user2@example.com', 'user3@example.com'],
self.eval(expression, data))
def test_iterable_dictionary_attribution(self):
data = yaql.tests.testdata.data
expression = "$.services.'com.mirantis.murano.yaql.name'"
self.assertEquals(['Service1', 'Service2', 'Service3', 'Service4'],
self.eval(expression, data))

View File

@ -0,0 +1,95 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import unittest
from yaql.language.exceptions import YaqlException
from yaql.tests import YaqlTest
from yaql.language.engine import context_aware
def f4(self):
return 'f4({0})'.format(self)
@context_aware
def f3(self, context):
context.register_function(f4, 'f4')
return 'f3({0})'.format(self)
@context_aware
def f2(self, context):
context.register_function(f3, 'f3')
return 'f2({0})'.format(self)
@context_aware
def f1(self, context):
context.register_function(f2, 'f2')
return 'f1({0})'.format(self)
@context_aware
def override_with_caps(self, context):
context.register_function(lambda self: "data is: " + self.upper(), 'print')
return self
def _print(self):
return "data is: %s" % self
class TestExecutionChain(YaqlTest):
def setUp(self):
super(TestExecutionChain, self).setUp()
self.context.register_function(f1, 'f1')
self.context.register_function(_print, 'print')
self.context.register_function(override_with_caps, 'caps_on')
def test_chain1(self):
expression = 'f1(abc).f2().f3()'
self.assertEquals('f3(f2(f1(abc)))', self.eval(expression))
def test_chain2(self):
expression = 'abc.f1().f2().f3().f4()'
self.assertEquals('f4(f3(f2(f1(abc))))', self.eval(expression))
def test_chain3(self):
expression = 'abc.f2().f3()'
self.assertRaises(YaqlException, self.eval, expression)
def test_chain4(self):
expression = 'abc.f4().f3().f2().f1()'
self.assertRaises(YaqlException, self.eval, expression)
def test_chain5(self):
expression = 'abc.f1() + abc.f2()'
self.assertRaises(YaqlException, self.eval, expression)
def test_override(self):
expression1 = 'abc.print()'
expression2 = 'abc.caps_on().print()'
self.assertEquals("data is: abc", self.eval(expression1))
self.assertEquals("data is: ABC", self.eval(expression2))
def test_override_and_forget(self):
expression = "abc.caps_on().print() + ', but ' + abc.print()"
self.assertEquals("data is: ABC, but data is: abc",
self.eval(expression))
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,34 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from yaql.tests import YaqlTest
import unittest
class TestStrings(YaqlTest):
def test_string_concat(self):
expression = "abc + cdef + ' qw er'"
self.assertEquals('abccdef qw er', self.eval(expression))
def test_string_to_list(self):
expression = "abc.asList()"
expression2 = "abc.asList()[1]"
self.assertEquals(['a', 'b', 'c'], self.eval(expression))
self.assertEquals('b', self.eval(expression2))
if __name__ == '__main__':
unittest.main()

172
yaql/tests/test_system.py Normal file
View File

@ -0,0 +1,172 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import types
import unittest
from yaql.tests import YaqlTest
import yaql
from yaql.language.engine import parameter
class TestSystem(YaqlTest):
def test_string_concat(self):
self.assertEquals("abcqwe", self.eval('abc + qwe'))
self.assertEquals("abc qwe", self.eval("abc + ' ' + qwe"))
def test_get_context_data(self):
obj = object()
self.assertEquals(obj, self.eval('$', obj))
def test_get_object_attribution(self):
class Foo(object):
def __init__(self, value):
self.bar = value
foo = Foo(42)
self.assertEquals(42, self.eval('$.bar', foo))
bar = Foo(foo)
self.assertEquals(42, self.eval('$.bar.bar', bar))
def test_missing_object_property_attribution(self):
class Foo(object):
def __init__(self, value):
self.bar = value
foo = Foo(42)
self.assertRaises(Exception, self.eval, '$.foo.missing', foo)
self.assertRaises(Exception, self.eval, '$.foo.missing',
{'foo': 'bar'})
def test_int_bool_resolving(self):
@parameter('param', arg_type=types.IntType)
def int_func(param):
return "int: " + str(param)
@parameter('param', arg_type=types.BooleanType)
def bool_func(param):
return "bool: " + str(param)
context1 = yaql.create_context(False)
context2 = yaql.create_context(False)
context3 = yaql.create_context(False)
context4 = yaql.create_context(False)
context1.register_function(int_func, 'foo')
context2.register_function(bool_func, 'foo')
context3.register_function(int_func, 'foo')
context3.register_function(bool_func, 'foo')
context4.register_function(bool_func, 'foo')
context4.register_function(int_func, 'foo')
self.assertEquals("int: 1", self.eval('foo(1)', context=context1))
self.assertEquals("int: 0", self.eval('foo(0)', context=context1))
self.assertRaises(Exception, self.eval, "foo('1')", context=context1)
self.assertRaises(Exception, self.eval, 'foo(1)', context=context2)
self.assertEquals("bool: True",
self.eval('foo(true)', context=context2))
self.assertEquals("bool: False",
self.eval('foo(false)', context=context2))
self.assertRaises(Exception, self.eval, "foo(1)", context=context2)
self.assertRaises(Exception, self.eval, 'foo(0)', context=context2)
self.assertRaises(Exception, self.eval, 'foo(True)', context=context2)
self.assertRaises(Exception, self.eval, "foo('true')",
context=context2)
self.assertEquals("int: 1", self.eval('foo(1)', context=context3))
self.assertEquals("int: 0", self.eval('foo(0)', context=context3))
self.assertEquals("bool: True",
self.eval('foo(true)', context=context3))
self.assertEquals("bool: False",
self.eval('foo(false)', context=context3))
self.assertEquals("int: 1", self.eval('foo(1)', context=context4))
self.assertEquals("int: 0", self.eval('foo(0)', context=context4))
self.assertEquals("bool: True",
self.eval('foo(true)', context=context4))
self.assertEquals("bool: False",
self.eval('foo(false)', context=context4))
def test_get_dict_attribution(self):
d = {
'key1': 'string1',
'key2': {
'inner': {
'last': 42,
'lastString': 'string'
}
},
'composite key': 3
}
self.assertEquals('string1', self.eval('$.key1', d))
self.assertEquals('string', self.eval('$.key2.inner.lastString', d))
self.assertEquals(42, self.eval('$.key2.inner.last', d))
self.assertEquals(3, self.eval("$.'composite key'", d))
def test_missing_key_dict_attributions(self):
d = {
'key1': 'string1',
'key2': {
'inner': {
'last': 42,
'lastString': 'string'
}
},
'composite key': 3
}
self.assertEquals(None, self.eval("$.'missing key'", d))
self.assertEquals(None, self.eval("$.key2.missing", d))
def test_function_call(self):
def foo():
return 42
self.context.register_function(foo, 'test')
self.assertEquals(42, self.eval("test()"))
def test_return_same_function(self):
def foo(bar):
return bar
self.context.register_function(foo, 'foo')
self.assertEquals('bar', self.eval('foo(bar)'))
def test_return_same_method(self):
def foo(self):
return self
self.context.register_function(foo, 'foo')
self.assertEquals('bar', self.eval('bar.foo()'))
def test_self_reordering(self):
def concat_right(self, arg):
return self + ',' + arg
@parameter('self', is_self=True)
def concat_left(arg, self):
return arg + ',' + self
self.context.register_function(concat_right, 'concat1')
self.context.register_function(concat_left, 'concat2')
self.assertEquals('abc,qwe', self.eval('abc.concat1(qwe)'))
self.assertEquals('qwe,abc', self.eval('abc.concat2(qwe)'))
def test_parenthesis(self):
expression = '(2+3)*2'
self.assertEquals(10, self.eval(expression))
if __name__ == '__main__':
unittest.main()

59
yaql/tests/test_tuples.py Normal file
View File

@ -0,0 +1,59 @@
# Copyright (c) 2014 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import unittest
from yaql.tests import YaqlTest
class TestTuples(YaqlTest):
def test_build_tuple(self):
expression = 'a=>b'
self.assertEquals(('a', 'b'), self.eval(expression))
def test_build_triple_tuple(self):
expression = 'a=>b=>c'
self.assertEquals(('a', 'b', 'c'), self.eval(expression))
def test_build_5x_tuple(self):
expression = 'a=>b=>c=>d=>e'
self.assertEquals(('a', 'b', 'c', 'd', 'e'), self.eval(expression))
def test_build_nested_tuple1(self):
expression = 'a=>(b=>c)'
self.assertEquals(('a', ('b', 'c')), self.eval(expression))
def test_build_nested_tuple2(self):
expression = '(a=>b)=>(c=>d)'
self.assertEquals((('a', 'b'), ('c', 'd')), self.eval(expression))
def test_build_nested_tuple3(self):
expression = '(a=>b)=>(c=>d)=>(e=>f)'
self.assertEquals((('a', 'b'), ('c', 'd'), ('e', 'f')),
self.eval(expression))
def test_build_nested_tuple4(self):
expression = 'a=>(b=>c)=>(d=>(e=>f=>g=>h))=>i'
self.assertEquals(('a', ('b', 'c'), ('d', ('e', 'f', 'g', 'h')), 'i'),
self.eval(expression))
def test_tuple_precedence(self):
expression1 = 'a=>2+3'
expression2 = '2+3=>a'
self.assertEquals(('a', 5), self.eval(expression1))
self.assertEquals((5, 'a'), self.eval(expression2))
if __name__ == '__main__':
unittest.main()

View File

@ -12,20 +12,23 @@
# License for the specific language governing permissions and limitations
# under the License.
import examples.ns.definition
# DEPRECATED. Use cli to run samples
def process_customer(customer):
return customer.email
class Customer():
def __init__(self, _id, email):
self.id = _id
self.email = email
self.list_prop = [1, 2, 3, 4]
ns = {'com.examples.test.Symbol': 'Some Test NS-based data'}
users = [Customer(1, 'user1@yandex.ru'),
Customer(2, 'user2@yandex.ru'),
Customer(3, 'user3@yandex.ru')]
users = [Customer(1, 'user1@example.com'),
Customer(2, 'user2@example.com'),
Customer(3, 'user3@example.com')]
services = [
{
@ -65,15 +68,4 @@ services = [
]
data = {'users': users, 'services': services}
examples.ns.definition.register_shortening('yaql', 'com.mirantis.murano.yaql')
examples.ns.definition.register_shortening('ex', 'com.mirantis.murano.examples')
examples.ns.definition.register_symbol('com.mirantis.murano.yaql', 'version')
examples.ns.definition.register_symbol('com.mirantis.murano.yaql', 'name')
examples.ns.definition.register_symbol('com.mirantis.murano.yaql', 'position')
examples.ns.definition.register_symbol('com.mirantis.murano.yaql', 'description')
examples.ns.definition.register_symbol('com.mirantis.murano.yaql', 'owner')
examples.ns.definition.register_symbol('com.mirantis.murano.yaql', 'parent_service')
examples.ns.definition.register_symbol('com.mirantis.murano.examples', 'Service0')
examples.ns.definition.register_symbol('com.mirantis.murano.examples', 'Service1')
data = {'users': users, 'services': services, 'ns': ns}