Major 0.3 changes implemented
This commit is contained in:
parent
635c2de97d
commit
3a18057f20
@ -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_:')
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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> "
|
||||
|
||||
|
@ -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)
|
||||
|
95
yaql/functions/arithmetic.py
Normal file
95
yaql/functions/arithmetic.py
Normal 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_!=')
|
@ -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')
|
93
yaql/functions/containers.py
Normal file
93
yaql/functions/containers.py
Normal 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_=>')
|
1
yaql/functions/old/__init__.py
Normal file
1
yaql/functions/old/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__author__ = 'ativelkov'
|
173
yaql/functions/old/builtin.py
Normal file
173
yaql/functions/old/builtin.py
Normal 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')
|
@ -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)
|
@ -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
30
yaql/functions/strings.py
Normal 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
64
yaql/functions/system.py
Normal 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
13
yaql/language/__init__.py
Normal 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.
|
@ -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
243
yaql/language/engine.py
Normal 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)
|
@ -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):
|
@ -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):
|
@ -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:
|
@ -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):
|
@ -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
30
yaql/tests/__init__.py
Normal 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
|
54
yaql/tests/test_arithmetic.py
Normal file
54
yaql/tests/test_arithmetic.py
Normal 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()
|
67
yaql/tests/test_collections.py
Normal file
67
yaql/tests/test_collections.py
Normal 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))
|
95
yaql/tests/test_execution_chains.py
Normal file
95
yaql/tests/test_execution_chains.py
Normal 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()
|
34
yaql/tests/test_strings.py
Normal file
34
yaql/tests/test_strings.py
Normal 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
172
yaql/tests/test_system.py
Normal 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
59
yaql/tests/test_tuples.py
Normal 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()
|
@ -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}
|
Loading…
x
Reference in New Issue
Block a user