Support for *args/**kwargs was added to the MuranoPL

With this change MuranoPL is going to have abilities similar to
what Python has with *args/**kwargs.

The syntax is "Usage: VarArgs" and "Usage: KwArgs" applied to
the method arguments.

Implements blueprint: support-for-args-kwargs-in-muranopl

Change-Id: I1c19bde100778639ac1d31b0c9076d35999266ee
This commit is contained in:
Stan Lagun 2016-04-25 13:38:13 -07:00
parent c4bf6f8760
commit 789dabb1c0
9 changed files with 248 additions and 31 deletions

View File

@ -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

View File

@ -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):

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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))

View 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]

View 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)

View 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.