Merge "Support for *args/**kwargs was added to the MuranoPL"
This commit is contained in:
commit
d0ad7fbd22
@ -57,6 +57,13 @@ class MethodUsages(object):
|
||||
StaticMethods = {Static, Extension}
|
||||
|
||||
|
||||
class MethodArgumentUsages(object):
|
||||
Standard = 'Standard'
|
||||
VarArgs = 'VarArgs'
|
||||
KwArgs = 'KwArgs'
|
||||
All = {Standard, VarArgs, KwArgs}
|
||||
|
||||
|
||||
class MuranoType(object):
|
||||
pass
|
||||
|
||||
|
@ -207,13 +207,39 @@ class MuranoDslExecutor(object):
|
||||
def _canonize_parameters(arguments_scheme, args, kwargs,
|
||||
method_name, receiver):
|
||||
arg_names = list(arguments_scheme.keys())
|
||||
parameter_values = utils.filter_parameters_dict(kwargs)
|
||||
if len(args) > len(arg_names):
|
||||
raise yaql_exceptions.NoMatchingMethodException(
|
||||
method_name, receiver)
|
||||
parameter_values = {}
|
||||
varargs_arg = None
|
||||
vararg_values = []
|
||||
kwargs_arg = None
|
||||
kwarg_values = {}
|
||||
for name, definition in six.iteritems(arguments_scheme):
|
||||
if definition.usage == dsl_types.MethodArgumentUsages.VarArgs:
|
||||
varargs_arg = name
|
||||
parameter_values[name] = vararg_values
|
||||
elif definition.usage == dsl_types.MethodArgumentUsages.KwArgs:
|
||||
kwargs_arg = name
|
||||
parameter_values[name] = kwarg_values
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
name = arg_names[i]
|
||||
parameter_values[name] = arg
|
||||
name = None if i >= len(arg_names) else arg_names[i]
|
||||
if name is None or name in (varargs_arg, kwargs_arg):
|
||||
if varargs_arg:
|
||||
vararg_values.append(arg)
|
||||
else:
|
||||
raise yaql_exceptions.NoMatchingMethodException(
|
||||
method_name, receiver)
|
||||
else:
|
||||
parameter_values[name] = arg
|
||||
|
||||
for name, value in six.iteritems(utils.filter_parameters_dict(kwargs)):
|
||||
if name in arguments_scheme and name not in (
|
||||
varargs_arg, kwargs_arg):
|
||||
parameter_values[name] = value
|
||||
elif kwargs_arg:
|
||||
kwarg_values[name] = value
|
||||
else:
|
||||
raise yaql_exceptions.NoMatchingMethodException(
|
||||
method_name, receiver)
|
||||
return tuple(), parameter_values
|
||||
|
||||
def load(self, data):
|
||||
|
@ -85,13 +85,36 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
|
||||
arguments_scheme = [{key: value} for key, value in
|
||||
six.iteritems(arguments_scheme)]
|
||||
self._arguments_scheme = collections.OrderedDict()
|
||||
seen_varargs = False
|
||||
seen_kwargs = False
|
||||
args_order_error = False
|
||||
for record in arguments_scheme:
|
||||
if (not isinstance(record, dict) or
|
||||
len(record) > 1):
|
||||
raise ValueError()
|
||||
if not isinstance(record, dict) or len(record) > 1:
|
||||
raise exceptions.DslSyntaxError(
|
||||
'Invalid arguments declaration')
|
||||
name = list(record.keys())[0]
|
||||
self._arguments_scheme[name] = MuranoMethodArgument(
|
||||
argument = MuranoMethodArgument(
|
||||
self, self.name, name, record[name])
|
||||
usage = argument.usage
|
||||
if (usage == dsl_types.MethodArgumentUsages.Standard and
|
||||
(seen_kwargs or seen_varargs)):
|
||||
args_order_error = True
|
||||
elif usage == dsl_types.MethodArgumentUsages.VarArgs:
|
||||
if seen_kwargs or seen_varargs:
|
||||
args_order_error = True
|
||||
seen_varargs = True
|
||||
elif usage == dsl_types.MethodArgumentUsages.KwArgs:
|
||||
if seen_kwargs:
|
||||
args_order_error = True
|
||||
seen_kwargs = True
|
||||
|
||||
if args_order_error:
|
||||
raise exceptions.DslSyntaxError(
|
||||
'Invalid argument order in method {0}'.format(
|
||||
self.name))
|
||||
else:
|
||||
self._arguments_scheme[name] = argument
|
||||
|
||||
self._meta = meta.MetaData(
|
||||
payload.get('Meta'),
|
||||
dsl_types.MetaTargets.Method,
|
||||
@ -183,6 +206,14 @@ class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec,
|
||||
self._meta = meta.MetaData(
|
||||
declaration.get('Meta'),
|
||||
dsl_types.MetaTargets.Argument, self.murano_method.declaring_type)
|
||||
self._usage = declaration.get('Usage') or \
|
||||
dsl_types.MethodArgumentUsages.Standard
|
||||
|
||||
if self._usage not in dsl_types.MethodArgumentUsages.All:
|
||||
raise exceptions.DslSyntaxError(
|
||||
'Unknown usage {0}. Must be one of ({1})'.format(
|
||||
self._usage, ', '.join(dsl_types.MethodArgumentUsages.All)
|
||||
))
|
||||
|
||||
def transform(self, value, this, *args, **kwargs):
|
||||
try:
|
||||
@ -207,6 +238,10 @@ class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec,
|
||||
def name(self):
|
||||
return self._arg_name
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
return self._usage
|
||||
|
||||
def get_meta(self, context):
|
||||
executor = helpers.get_executor(context)
|
||||
context = executor.create_type_context(
|
||||
|
@ -30,6 +30,11 @@ class MuranoProperty(dsl_types.MuranoProperty, typespec.Spec,
|
||||
super(MuranoProperty, self).__init__(declaration, declaring_type)
|
||||
self._property_name = property_name
|
||||
self._declaring_type = weakref.ref(declaring_type)
|
||||
self._usage = declaration.get('Usage') or dsl_types.PropertyUsages.In
|
||||
if self._usage not in dsl_types.PropertyUsages.All:
|
||||
raise exceptions.DslSyntaxError(
|
||||
'Unknown usage {0}. Must be one of ({1})'.format(
|
||||
self._usage, ', '.join(dsl_types.PropertyUsages.All)))
|
||||
self._meta = meta.MetaData(
|
||||
declaration.get('Meta'),
|
||||
dsl_types.MetaTargets.Property, declaring_type)
|
||||
@ -53,6 +58,10 @@ class MuranoProperty(dsl_types.MuranoProperty, typespec.Spec,
|
||||
def name(self):
|
||||
return self._property_name
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
return self._usage
|
||||
|
||||
def get_meta(self, context):
|
||||
def meta_producer(cls):
|
||||
prop = cls.properties.get(self.name)
|
||||
|
@ -15,7 +15,6 @@
|
||||
import weakref
|
||||
|
||||
from murano.dsl import dsl_types
|
||||
from murano.dsl import exceptions
|
||||
from murano.dsl import helpers
|
||||
from murano.dsl import type_scheme
|
||||
|
||||
@ -24,13 +23,8 @@ class Spec(object):
|
||||
def __init__(self, declaration, container_type):
|
||||
self._container_type = weakref.ref(container_type)
|
||||
self._contract = type_scheme.TypeScheme(declaration['Contract'])
|
||||
self._usage = declaration.get('Usage') or dsl_types.PropertyUsages.In
|
||||
self._default = declaration.get('Default')
|
||||
self._has_default = 'Default' in declaration
|
||||
if self._usage not in dsl_types.PropertyUsages.All:
|
||||
raise exceptions.DslSyntaxError(
|
||||
'Unknown type {0}. Must be one of ({1})'.format(
|
||||
self._usage, ', '.join(dsl_types.PropertyUsages.All)))
|
||||
self._default = declaration.get('Default')
|
||||
|
||||
def transform(self, value, this, owner, context, default=None):
|
||||
if default is None:
|
||||
@ -64,7 +58,3 @@ class Spec(object):
|
||||
@property
|
||||
def has_default(self):
|
||||
return self._has_default
|
||||
|
||||
@property
|
||||
def usage(self):
|
||||
return self._usage
|
||||
|
@ -335,21 +335,33 @@ def _create_basic_mpl_stub(murano_method, reserve_params, payload,
|
||||
fd = specs.FunctionDefinition(
|
||||
murano_method.name, payload, is_function=False, is_method=True)
|
||||
|
||||
i = 0
|
||||
for i, (name, arg_spec) in enumerate(
|
||||
six.iteritems(murano_method.arguments_scheme), reserve_params + 1):
|
||||
i = reserve_params + 1
|
||||
varargs = False
|
||||
kwargs = False
|
||||
for name, arg_spec in six.iteritems(murano_method.arguments_scheme):
|
||||
position = i
|
||||
if arg_spec.usage == dsl_types.MethodArgumentUsages.VarArgs:
|
||||
name = '*'
|
||||
varargs = True
|
||||
elif arg_spec.usage == dsl_types.MethodArgumentUsages.KwArgs:
|
||||
name = '**'
|
||||
position = None
|
||||
kwargs = True
|
||||
p = specs.ParameterDefinition(
|
||||
name, ContractedValue(arg_spec, with_check=check_first_arg),
|
||||
position=i, default=dsl.NO_VALUE)
|
||||
position=position, default=dsl.NO_VALUE)
|
||||
check_first_arg = False
|
||||
fd.parameters[name] = p
|
||||
i += 1
|
||||
|
||||
fd.parameters['*'] = specs.ParameterDefinition(
|
||||
'*',
|
||||
value_type=yaqltypes.PythonType(object, nullable=True),
|
||||
position=i)
|
||||
fd.parameters['**'] = specs.ParameterDefinition(
|
||||
'**', value_type=yaqltypes.PythonType(object, nullable=True))
|
||||
if not varargs:
|
||||
fd.parameters['*'] = specs.ParameterDefinition(
|
||||
'*',
|
||||
value_type=yaqltypes.PythonType(object, nullable=True),
|
||||
position=i)
|
||||
if not kwargs:
|
||||
fd.parameters['**'] = specs.ParameterDefinition(
|
||||
'**', value_type=yaqltypes.PythonType(object, nullable=True))
|
||||
|
||||
fd.set_parameter(specs.ParameterDefinition(
|
||||
'__context', yaqltypes.Context(), 0))
|
||||
|
65
murano/tests/unit/dsl/meta/TestVarKwArgs.yaml
Normal file
65
murano/tests/unit/dsl/meta/TestVarKwArgs.yaml
Normal file
@ -0,0 +1,65 @@
|
||||
Name: TestVarKwArgs
|
||||
|
||||
Methods:
|
||||
testVarArgs:
|
||||
Body:
|
||||
Return: $.varArgsMethod(1, 2, 3, 4)
|
||||
|
||||
testVarArgsContract:
|
||||
Body:
|
||||
Return: $.varArgsMethod(1, string)
|
||||
|
||||
testDuplicateVarArgs:
|
||||
Body:
|
||||
Return: $.varArgsMethod(1, arg1 => 2)
|
||||
|
||||
testExplicitVarArgs:
|
||||
Body:
|
||||
Return: $.varArgsMethod(1, rest => 2)
|
||||
|
||||
varArgsMethod:
|
||||
Arguments:
|
||||
- arg1:
|
||||
Contract: $.int()
|
||||
- rest:
|
||||
Contract: $.int()
|
||||
Usage: VarArgs
|
||||
Body:
|
||||
Return: $rest
|
||||
|
||||
testKwArgs:
|
||||
Body:
|
||||
Return: $.kwArgsMethod(arg1 => 1, arg2 => 2, arg3 => 3)
|
||||
|
||||
testKwArgsContract:
|
||||
Body:
|
||||
Return: $.kwArgsMethod(arg1 => 1, arg2 => string)
|
||||
|
||||
testDuplicateKwArgs:
|
||||
Body:
|
||||
Return: $.kwArgsMethod(1, arg1 => 2)
|
||||
|
||||
kwArgsMethod:
|
||||
Arguments:
|
||||
- arg1:
|
||||
Contract: $.int()
|
||||
- rest:
|
||||
Contract: $.int()
|
||||
Usage: KwArgs
|
||||
Body:
|
||||
Return: $rest
|
||||
|
||||
testArgs:
|
||||
Body:
|
||||
Return: $.argsMethod(1, 2, 3, arg1 => 4, arg2 => 5, arg3 => 6)
|
||||
|
||||
argsMethod:
|
||||
Arguments:
|
||||
- args:
|
||||
Contract: $.int()
|
||||
Usage: VarArgs
|
||||
- kwargs:
|
||||
Contract: $.int()
|
||||
Usage: KwArgs
|
||||
Body:
|
||||
Return: [$args, $kwargs]
|
62
murano/tests/unit/dsl/test_varkwargs.py
Normal file
62
murano/tests/unit/dsl/test_varkwargs.py
Normal file
@ -0,0 +1,62 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) 2015 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 import exceptions as yaql_exceptions
|
||||
|
||||
from murano.dsl import exceptions as dsl_exceptions
|
||||
from murano.tests.unit.dsl.foundation import object_model as om
|
||||
from murano.tests.unit.dsl.foundation import test_case
|
||||
|
||||
|
||||
class TestVarKwArgs(test_case.DslTestCase):
|
||||
def setUp(self):
|
||||
super(TestVarKwArgs, self).setUp()
|
||||
self._runner = self.new_runner(om.Object('TestVarKwArgs'))
|
||||
|
||||
def test_varargs(self):
|
||||
self.assertEqual([2, 3, 4], self._runner.testVarArgs())
|
||||
|
||||
def test_kwargs(self):
|
||||
self.assertEqual({'arg2': 2, 'arg3': 3}, self._runner.testKwArgs())
|
||||
|
||||
def test_duplicate_kwargs(self):
|
||||
self.assertRaises(
|
||||
yaql_exceptions.NoMatchingMethodException,
|
||||
self._runner.testDuplicateKwArgs)
|
||||
|
||||
def test_duplicate_varargs(self):
|
||||
self.assertRaises(
|
||||
yaql_exceptions.NoMatchingMethodException,
|
||||
self._runner.testDuplicateVarArgs)
|
||||
|
||||
def test_explicit_varargs(self):
|
||||
self.assertRaises(
|
||||
yaql_exceptions.NoMatchingMethodException,
|
||||
self._runner.testExplicitVarArgs)
|
||||
|
||||
def test_args(self):
|
||||
self.assertEqual(
|
||||
[[1, 2, 3], {'arg1': 4, 'arg2': 5, 'arg3': 6}],
|
||||
self._runner.testArgs())
|
||||
|
||||
def test_varargs_contract(self):
|
||||
self.assertRaises(
|
||||
dsl_exceptions.ContractViolationException,
|
||||
self._runner.testVarArgsContract)
|
||||
|
||||
def test_kwargs_contract(self):
|
||||
self.assertRaises(
|
||||
dsl_exceptions.ContractViolationException,
|
||||
self._runner.testKwArgsContract)
|
11
releasenotes/notes/var-kw-args-c42c31678d8bc747.yaml
Normal file
11
releasenotes/notes/var-kw-args-c42c31678d8bc747.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- >
|
||||
It is possible now to declare MuranoPL YAML methods with variable length
|
||||
positional and keyword arguments. This is done using argument Usage
|
||||
attribute. Regular arguments have Standard usage which is the default.
|
||||
Variable length args (args in Python) should have "Usage: VarArgs" and
|
||||
keyword args (kwargs) are declared with "Usage: KwArgs". Inside the
|
||||
method they seen as a list and a dictionary correspondingly. For such
|
||||
arguments contracts are written for individual argument values thus
|
||||
no need to write them as lists/dicts.
|
Loading…
Reference in New Issue
Block a user