Merge "Extension methods were introduced to MuranoPL"

This commit is contained in:
Jenkins 2016-03-03 16:52:55 +00:00 committed by Gerrit Code Review
commit e5e58b2077
22 changed files with 555 additions and 89 deletions

View File

@ -27,6 +27,7 @@ CTX_CURRENT_EXCEPTION = '$?currentException'
CTX_CURRENT_METHOD = '$?currentMethod'
CTX_EXECUTOR = '$?executor'
CTX_EXECUTION_SESSION = '$?executionSession'
CTX_NAMES_SCOPE = '$?namesScope'
CTX_ORIGINAL_CONTEXT = '$?originalContext'
CTX_PACKAGE_LOADER = '$?packageLoader'
CTX_SKIP_FRAME = '$?skipFrame'

View File

@ -108,9 +108,11 @@ class InterfacesParameter(yaqltypes.HiddenParameterType,
class MuranoTypeParameter(yaqltypes.PythonType):
def __init__(self, base_type=None, nullable=False, context=None):
def __init__(self, base_type=None, nullable=False, context=None,
resolve_strings=True):
self._context = context
self._base_type = base_type
self._resolve_strings = resolve_strings
super(MuranoTypeParameter, self).__init__(
(dsl_types.MuranoTypeReference,
six.string_types), nullable)
@ -119,6 +121,10 @@ class MuranoTypeParameter(yaqltypes.PythonType):
if not super(MuranoTypeParameter, self).check(
value, context, *args, **kwargs):
return False
if isinstance(value, six.string_types):
if not self._resolve_strings:
return False
value = helpers.get_class(value, context).get_reference()
if isinstance(value, dsl_types.MuranoTypeReference):
if not self._base_type:
return True
@ -133,12 +139,7 @@ class MuranoTypeParameter(yaqltypes.PythonType):
value = super(MuranoTypeParameter, self).convert(
value, sender, context, function_spec, engine)
if isinstance(value, six.string_types):
if function_spec.meta.get(constants.META_MURANO_METHOD):
context = helpers.get_caller_context(context)
murano_type = helpers.get_type(context)
value = helpers.get_class(
murano_type.namespace_resolver.resolve_name(value),
context).get_reference()
value = helpers.get_class(value, context).get_reference()
if self._base_type and not self._base_type.is_compatible(value):
raise ValueError('Value must be subtype of {0}'.format(
self._base_type.name

View File

@ -50,7 +50,11 @@ class MethodUsages(object):
Action = 'Action'
Runtime = 'Runtime'
Static = 'Static'
All = {Action, Runtime, Static}
Extension = 'Extension'
All = {Action, Runtime, Static, Extension}
InstanceMethods = {Runtime, Action}
StaticMethods = {Static, Extension}
class MuranoType(object):

View File

@ -82,8 +82,16 @@ class MuranoDslExecutor(object):
self.create_object_context(this, context), method)
method_context[constants.CTX_SKIP_FRAME] = True
method_context[constants.CTX_ACTIONS_ONLY] = actions_only
return method.yaql_function_definition(
yaql_engine, method_context, this.real_this)(*args, **kwargs)
stub = method.static_stub if isinstance(
this, dsl_types.MuranoType) else method.instance_stub
if stub is None:
raise ValueError(
'Method {0} cannot be called on receiver {1}'.format(
method, this))
return stub(yaql_engine, method_context, this.real_this)(
*args, **kwargs)
if (context[constants.CTX_ACTIONS_ONLY] and method.usage !=
dsl_types.MethodUsages.Action):
@ -119,6 +127,8 @@ class MuranoDslExecutor(object):
return method.body(
yaql_engine, context, native_this)(*args, **kwargs)
else:
context[constants.CTX_NAMES_SCOPE] = \
method.declaring_type
return (None if method.body is None
else method.body.execute(context))
@ -312,9 +322,13 @@ class MuranoDslExecutor(object):
caller = caller_context
while caller is not None and caller[constants.CTX_SKIP_FRAME]:
caller = caller[constants.CTX_CALLER_CONTEXT]
context[constants.CTX_NAMES_SCOPE] = caller_context[
constants.CTX_NAMES_SCOPE]
context[constants.CTX_CALLER_CONTEXT] = caller
context[constants.CTX_ALLOW_PROPERTY_WRITES] = caller_context[
constants.CTX_ALLOW_PROPERTY_WRITES]
else:
context[constants.CTX_NAMES_SCOPE] = obj_type
return context
@staticmethod

View File

@ -216,10 +216,16 @@ def are_property_modifications_allowed(context=None):
return context[constants.CTX_ALLOW_PROPERTY_WRITES] or False
def get_names_scope(context=None):
context = context or get_context()
return context[constants.CTX_NAMES_SCOPE]
def get_class(name, context=None):
context = context or get_context()
murano_class = get_type(context)
return murano_class.package.find_class(name)
murano_type = get_names_scope(context)
name = murano_type.namespace_resolver.resolve_name(name)
return murano_type.package.find_class(name)
def get_current_thread_id():
@ -538,3 +544,11 @@ def function(c):
if hasattr(c, 'im_func'):
return c.im_func
return c
def list_value(v):
if v is None:
return []
if not yaqlutils.is_sequence(v):
v = [v]
return v

View File

@ -169,7 +169,8 @@ class LhsExpression(object):
def __call__(self, value, context):
new_context = self._create_context(context)
new_context[''] = context['$']
new_context[constants.CTX_TYPE] = context[constants.CTX_TYPE]
for name in (constants.CTX_NAMES_SCOPE,):
new_context[name] = context[name]
self._current_obj = None
self._current_obj_name = None
property = self._expression(context=new_context)

View File

@ -25,8 +25,7 @@ from murano.dsl import yaql_expression
class CodeBlock(expressions.DslExpression):
def __init__(self, body):
if not isinstance(body, list):
body = [body]
body = helpers.list_value(body)
self.code_block = list(map(expressions.parse_expression, body))
def execute(self, context):

View File

@ -32,10 +32,7 @@ class MetaProvider(object):
class MetaData(MetaProvider):
def __init__(self, definition, target, scope_type):
scope_type = weakref.ref(scope_type)
if not definition:
definition = []
elif not isinstance(definition, list):
definition = [definition]
definition = helpers.list_value(definition)
factories = []
used_types = set()
for d in definition:

View File

@ -50,14 +50,21 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
payload, weakref.proxy(self), original_name)
self._arguments_scheme = None
if any((
helpers.inspect_is_static(
declaring_type.extension_class, original_name),
helpers.inspect_is_classmethod(
declaring_type.extension_class, original_name))):
self._usage = dsl_types.MethodUsages.Static
helpers.inspect_is_static(
declaring_type.extension_class, original_name),
helpers.inspect_is_classmethod(
declaring_type.extension_class, original_name))):
self._usage = self._body.meta.get(
constants.META_USAGE, dsl_types.MethodUsages.Static)
if self._usage not in dsl_types.MethodUsages.StaticMethods:
raise ValueError(
'Invalid Usage for static method ' + self.name)
else:
self._usage = (self._body.meta.get(constants.META_USAGE) or
dsl_types.MethodUsages.Runtime)
if self._usage not in dsl_types.MethodUsages.InstanceMethods:
raise ValueError(
'Invalid Usage for instance method ' + self.name)
if (self._body.name.startswith('#') or
self._body.name.startswith('*')):
raise ValueError(
@ -68,10 +75,10 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
declaring_type)
else:
payload = payload or {}
self._body = macros.MethodBlock(payload.get('Body') or [], name)
self._body = macros.MethodBlock(payload.get('Body'), name)
self._usage = payload.get(
'Usage') or dsl_types.MethodUsages.Runtime
arguments_scheme = payload.get('Arguments') or []
arguments_scheme = helpers.list_value(payload.get('Arguments'))
if isinstance(arguments_scheme, dict):
arguments_scheme = [{key: value} for key, value in
six.iteritems(arguments_scheme)]
@ -87,8 +94,9 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
payload.get('Meta'),
dsl_types.MetaTargets.Method,
declaring_type)
self._yaql_function_definition = \
yaql_integration.build_wrapper_function_definition(
self._instance_stub, self._static_stub = \
yaql_integration.build_stub_function_definitions(
weakref.proxy(self))
@property
@ -104,8 +112,12 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
return self._arguments_scheme
@property
def yaql_function_definition(self):
return self._yaql_function_definition
def instance_stub(self):
return self._instance_stub
@property
def static_stub(self):
return self._static_stub
@property
def usage(self):
@ -121,7 +133,7 @@ class MuranoMethod(dsl_types.MuranoMethod, meta.MetaProvider):
@property
def is_static(self):
return self.usage == dsl_types.MethodUsages.Static
return self.usage in dsl_types.MethodUsages.StaticMethods
def get_meta(self, context):
def meta_producer(cls):
@ -168,9 +180,12 @@ class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec,
declaration.get('Meta'),
dsl_types.MetaTargets.Argument, self.murano_method.declaring_type)
def validate(self, *args, **kwargs):
def transform(self, value, this, *args, **kwargs):
try:
return super(MuranoMethodArgument, self).validate(*args, **kwargs)
if self.murano_method.usage == dsl_types.MethodUsages.Extension:
this = self.murano_method.declaring_type
return super(MuranoMethodArgument, self).transform(
value, this, *args, **kwargs)
except exceptions.ContractViolationException as e:
msg = u'[{0}::{1}({2}{3})] {4}'.format(
self.murano_method.declaring_type.name,

View File

@ -232,7 +232,7 @@ class MuranoObject(dsl_types.MuranoObject):
# default = helpers.evaluate(default, context)
obj = self.cast(spec.declaring_type)
values_to_assign.append((obj, spec.validate(
values_to_assign.append((obj, spec.transform(
value, self.real_this,
self.real_this, context, default=default)))
for obj, value in values_to_assign:

View File

@ -98,12 +98,11 @@ class MuranoPackage(dsl_types.MuranoPackage, dslmeta.MetaProvider):
return type_obj
if callable(data):
data = data()
if not utils.is_sequence(data):
data = [data]
data = helpers.list_value(data)
unnamed_class = None
last_ns = {}
for cls_data in data:
last_ns = cls_data.setdefault('Namespaces', last_ns)
last_ns = cls_data.setdefault('Namespaces', last_ns.copy())
if len(cls_data) == 1:
continue
cls_name = cls_data.get('Name')

View File

@ -34,9 +34,9 @@ class MuranoProperty(dsl_types.MuranoProperty, typespec.Spec,
dsl_types.MetaTargets.Property, declaring_type)
self._meta_values = None
def validate(self, *args, **kwargs):
def transform(self, *args, **kwargs):
try:
return super(MuranoProperty, self).validate(*args, **kwargs)
return super(MuranoProperty, self).transform(*args, **kwargs)
except exceptions.ContractViolationException as e:
msg = u'[{0}.{1}{2}] {3}'.format(
self.declaring_type.name, self.name, e.path, six.text_type(e))

View File

@ -51,7 +51,6 @@ class MuranoType(dsl_types.MuranoType):
return self._namespace_resolver
@abc.abstractproperty
@property
def usage(self):
raise NotImplementedError()
@ -66,7 +65,8 @@ class MuranoType(dsl_types.MuranoType):
class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
_allowed_usages = {dsl_types.ClassUsages.Class}
def __init__(self, ns_resolver, name, package, parents, meta=None):
def __init__(self, ns_resolver, name, package, parents, meta=None,
imports=None):
super(MuranoClass, self).__init__(ns_resolver, name, package)
self._methods = {}
self._properties = {}
@ -84,10 +84,12 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
u'Type {0} cannot have parent with Usage {1}'.format(
self.name, p.usage))
self._context = None
self._exported_context = None
self._parent_mappings = self._build_parent_remappings()
self._property_values = {}
self._meta = dslmeta.MetaData(meta, dsl_types.MetaTargets.Type, self)
self._meta_values = None
self._imports = list(self._resolve_imports(imports))
@property
def usage(self):
@ -126,6 +128,7 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
method = murano_method.MuranoMethod(self, name, payload, original_name)
self._methods[name] = method
self._context = None
self._exported_context = None
return method
@property
@ -155,10 +158,22 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
leaf = False
queue.append((p, path + segment))
if leaf:
path = path + segment
path += segment
if path:
yield path
def _resolve_imports(self, imports):
seen = {self.name}
for imp in helpers.list_value(imports):
if imp in seen:
continue
type = helpers.resolve_type(imp, self)
if type in seen:
continue
seen.add(imp)
seen.add(type)
yield type
def _choose_symbol(self, func):
chains = sorted(
self._find_symbol_chains(func, self),
@ -366,23 +381,51 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
@property
def context(self):
if not self._context:
self._context = yaql_integration.create_empty_context()
ctx = None
for imp in reversed(self._imports):
if ctx is None:
ctx = imp.exported_context
else:
ctx = helpers.link_contexts(ctx, imp.exported_context)
if ctx is None:
self._context = yaql_integration.create_empty_context()
else:
self._context = ctx.create_child_context()
for m in self._iterate_unique_methods():
self._context.register_function(
m.yaql_function_definition,
name=m.yaql_function_definition.name)
if m.instance_stub:
self._context.register_function(
m.instance_stub, name=m.instance_stub.name)
if m.static_stub:
self._context.register_function(
m.static_stub, name=m.static_stub.name)
return self._context
@property
def exported_context(self):
if not self._exported_context:
self._exported_context = yaql_integration.create_empty_context()
for m in self._iterate_unique_methods():
if m.usage == dsl_types.MethodUsages.Extension:
if m.instance_stub:
self._exported_context.register_function(
m.instance_stub, name=m.instance_stub.name)
if m.static_stub:
self._exported_context.register_function(
m.static_stub, name=m.static_stub.name)
return self._exported_context
def get_property(self, name, context):
prop = self.find_static_property(name)
cls = prop.declaring_type
value = cls._property_values.get(name, prop.default)
return prop.validate(value, cls, None, context)
return prop.transform(value, cls, None, context)
def set_property(self, name, value, context):
prop = self.find_static_property(name)
cls = prop.declaring_type
cls._property_values[name] = prop.validate(value, cls, None, context)
cls._property_values[name] = prop.transform(value, cls, None, context)
def get_meta(self, context):
if self._meta_values is None:
@ -394,9 +437,10 @@ class MuranoClass(dsl_types.MuranoClass, MuranoType, dslmeta.MetaProvider):
class MuranoMetaClass(dsl_types.MuranoMetaClass, MuranoClass):
_allowed_usages = {dsl_types.ClassUsages.Meta, dsl_types.ClassUsages.Class}
def __init__(self, ns_resolver, name, package, parents, meta=None):
def __init__(self, ns_resolver, name, package, parents, meta=None,
imports=None):
super(MuranoMetaClass, self).__init__(
ns_resolver, name, package, parents, meta)
ns_resolver, name, package, parents, meta, imports)
self._cardinality = dsl_types.MetaCardinality.One
self._targets = list(dsl_types.MetaCardinality.All)
self._inherited = False
@ -456,7 +500,7 @@ def _create_class(cls, name, ns_resolver, data, package, *args, **kwargs):
type_obj = cls(
ns_resolver, name, package, parent_classes, data.get('Meta'),
*args, **kwargs)
data.get('Import'), *args, **kwargs)
properties = data.get('Properties') or {}
for property_name, property_spec in six.iteritems(properties):

View File

@ -32,7 +32,8 @@ class TypeScheme(object):
self._spec = spec
@staticmethod
def prepare_context(root_context, this, owner, default, calling_type):
def prepare_transform_context(root_context, this, owner, default,
calling_type):
@specs.parameter('value', nullable=True)
@specs.method
def int_(value):
@ -150,7 +151,7 @@ class TypeScheme(object):
@specs.parameter('version_spec', yaqltypes.String(True))
@specs.method
def class_(value, name, default_name=None, version_spec=None):
object_store = this.object_store
object_store = None if this is None else this.object_store
if not default_name:
default_name = name
murano_class = name.type
@ -164,7 +165,7 @@ class TypeScheme(object):
obj = helpers.instantiate(
value, owner, object_store, root_context,
calling_type, default_name, default)
elif isinstance(value, six.string_types):
elif isinstance(value, six.string_types) and object_store:
obj = object_store.get(value)
if obj is None:
if not object_store.initializing:
@ -197,6 +198,66 @@ class TypeScheme(object):
context.register_function(not_owned)
return context
@staticmethod
def prepare_validate_context(root_context):
@specs.parameter('value', nullable=True)
@specs.method
def int_(value):
if value is None or isinstance(
value, int) and not isinstance(value, bool):
return value
raise exceptions.ContractViolationException()
@specs.parameter('value', nullable=True)
@specs.method
def string(value):
if value is None or isinstance(value, six.string_types):
return value
raise exceptions.ContractViolationException()
@specs.parameter('value', nullable=True)
@specs.method
def bool_(value):
if value is None or isinstance(value, bool):
return value
raise exceptions.ContractViolationException()
@specs.parameter('value', nullable=True)
@specs.method
def not_null(value):
if value is None:
raise exceptions.ContractViolationException()
return value
@specs.parameter('value', nullable=True)
@specs.parameter('predicate', yaqltypes.Lambda(with_context=True))
@specs.method
def check(value, predicate):
if predicate(root_context.create_child_context(), value):
return value
raise exceptions.ContractViolationException()
@specs.parameter('type', dsl.MuranoTypeParameter(
nullable=False, context=root_context))
@specs.parameter('value', nullable=True)
@specs.parameter('version_spec', yaqltypes.String(True))
@specs.method
def class_(value, type, version_spec=None):
if helpers.is_instance_of(
value, type.type.name,
version_spec or helpers.get_names_scope(root_context)):
return value
raise exceptions.ContractViolationException()
context = root_context.create_child_context()
context.register_function(int_)
context.register_function(string)
context.register_function(bool_)
context.register_function(check)
context.register_function(not_null)
context.register_function(class_)
return context
def _map_dict(self, data, spec, context, path):
if data is None or data is dsl.NO_VALUE:
data = {}
@ -298,7 +359,7 @@ class TypeScheme(object):
else:
return self._map_scalar(data, spec)
def __call__(self, data, context, this, owner, default, calling_type):
def transform(self, data, context, this, owner, default, calling_type):
# TODO(ativelkov, slagun): temporary fix, need a better way of handling
# composite defaults
# A bug (#1313694) has been filed
@ -306,10 +367,21 @@ class TypeScheme(object):
if data is dsl.NO_VALUE:
data = helpers.evaluate(default, context)
context = self.prepare_context(
context = self.prepare_transform_context(
context, this, owner, default, calling_type)
return self._map(data, self._spec, context, '')
def validate(self, data, context, default):
if data is dsl.NO_VALUE:
data = helpers.evaluate(default, context)
context = self.prepare_validate_context(context)
try:
self._map(data, self._spec, context, '')
return True
except exceptions.ContractViolationException:
return False
def format_scalar(value):
if isinstance(value, six.string_types):

View File

@ -32,20 +32,25 @@ class Spec(object):
'Unknown type {0}. Must be one of ({1})'.format(
self._usage, ', '.join(dsl_types.PropertyUsages.All)))
def validate(self, value, this, owner, context, default=None):
def transform(self, value, this, owner, context, default=None):
if default is None:
default = self.default
executor = helpers.get_executor(context)
if isinstance(this, dsl_types.MuranoType):
return self._contract(
return self._contract.transform(
value, executor.create_object_context(this),
None, None, default, helpers.get_type(context))
else:
return self._contract(
return self._contract.transform(
value, executor.create_object_context(
this.cast(self._container_type())),
this, owner, default, helpers.get_type(context))
def validate(self, value, context, default=None):
if default is None:
default = self.default
return self._contract.validate(value, context, default)
@property
def default(self):
return self._default

View File

@ -183,10 +183,7 @@ def op_dot_static(context, receiver, expr, operator):
@specs.parameter('name', yaqltypes.Keyword())
@specs.name('#operator_:')
def ns_resolve(context, prefix, name):
murano_type = helpers.get_type(context)
return helpers.get_class(
murano_type.namespace_resolver.resolve_name(
prefix + ':' + name), context).get_reference()
return helpers.get_class(prefix + ':' + name, context).get_reference()
@specs.parameter('name', yaqltypes.Keyword())

View File

@ -26,6 +26,7 @@ from yaql import legacy
from murano.dsl import constants
from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import helpers
from murano.dsl import yaql_functions
@ -79,16 +80,21 @@ ROOT_CONTEXT_12 = yaql.create_context(
class ContractedValue(yaqltypes.GenericType):
def __init__(self, value_spec):
self._value_spec = value_spec
self._last_result = False
def __init__(self, value_spec, with_check=False):
def converter(value, receiver, context, *args, **kwargs):
if isinstance(receiver, dsl_types.MuranoObject):
this = receiver.real_this
else:
this = receiver
return value_spec.transform(
value, this, context[constants.CTX_ARGUMENT_OWNER],
context)
def checker(value, context, *args, **kwargs):
return value_spec.validate(value, context)
super(ContractedValue, self).__init__(
True, None,
lambda value, receiver, context, *args, **kwargs:
self._value_spec.validate(
value, receiver.real_this,
context[constants.CTX_ARGUMENT_OWNER], context))
True, checker if with_check else None, converter)
def convert(self, value, *args, **kwargs):
if value is None:
@ -179,10 +185,14 @@ def get_function_definition(func, murano_method, original_name):
def payload(__context, __self, *args, **kwargs):
with helpers.contextual(__context):
__context[constants.CTX_NAMES_SCOPE] = \
murano_method.declaring_type
return body(__self.extension, *args, **kwargs)
def static_payload(__context, __receiver, *args, **kwargs):
with helpers.contextual(__context):
__context[constants.CTX_NAMES_SCOPE] = \
murano_method.declaring_type
return body(*args, **kwargs)
if is_static:
@ -211,20 +221,22 @@ def _remove_first_parameter(fd):
p.position -= 1
def build_wrapper_function_definition(murano_method):
def build_stub_function_definitions(murano_method):
if isinstance(murano_method.body, specs.FunctionDefinition):
return _build_native_wrapper_function_definition(murano_method)
return _build_native_stub_function_definitions(murano_method)
else:
return _build_mpl_wrapper_function_definition(murano_method)
return _build_mpl_stub_function_definitions(murano_method)
def _build_native_wrapper_function_definition(murano_method):
def _build_native_stub_function_definitions(murano_method):
runtime_version = murano_method.declaring_type.package.runtime_version
engine = choose_yaql_engine(runtime_version)
@specs.method
@specs.name(murano_method.name)
@specs.meta(constants.META_MURANO_METHOD, murano_method)
@specs.parameter('__receiver', yaqltypes.NotOfType(
dsl_types.MuranoTypeReference))
def payload(__context, __receiver, *args, **kwargs):
executor = helpers.get_executor(__context)
args = tuple(dsl.to_mutable(arg, engine) for arg in args)
@ -232,35 +244,106 @@ def _build_native_wrapper_function_definition(murano_method):
return helpers.evaluate(murano_method.invoke(
executor, __receiver, args, kwargs, __context, True), __context)
return specs.get_function_definition(payload)
@specs.method
@specs.name(murano_method.name)
@specs.meta(constants.META_MURANO_METHOD, murano_method)
@specs.parameter('__receiver', yaqltypes.NotOfType(
dsl_types.MuranoTypeReference))
def extension_payload(__context, __receiver, *args, **kwargs):
executor = helpers.get_executor(__context)
args = tuple(dsl.to_mutable(arg, engine) for arg in args)
kwargs = dsl.to_mutable(kwargs, engine)
return helpers.evaluate(murano_method.invoke(
executor, murano_method.declaring_type,
(__receiver,) + args, kwargs, __context, True), __context)
@specs.method
@specs.name(murano_method.name)
@specs.meta(constants.META_MURANO_METHOD, murano_method)
@specs.parameter('__receiver', dsl_types.MuranoTypeReference)
def static_payload(__context, __receiver, *args, **kwargs):
executor = helpers.get_executor(__context)
args = tuple(dsl.to_mutable(arg, engine) for arg in args)
kwargs = dsl.to_mutable(kwargs, engine)
return helpers.evaluate(murano_method.invoke(
executor, __receiver, args, kwargs, __context, True), __context)
if murano_method.usage in dsl_types.MethodUsages.InstanceMethods:
return specs.get_function_definition(payload), None
elif murano_method.usage == dsl_types.MethodUsages.Static:
return (specs.get_function_definition(payload),
specs.get_function_definition(static_payload))
elif murano_method.usage == dsl_types.MethodUsages.Extension:
return (specs.get_function_definition(extension_payload),
specs.get_function_definition(static_payload))
else:
raise ValueError('Unknown method usage ' + murano_method.usage)
def _build_mpl_wrapper_function_definition(murano_method):
def _build_mpl_stub_function_definitions(murano_method):
if murano_method.usage in dsl_types.MethodUsages.InstanceMethods:
return _create_instance_mpl_stub(murano_method), None
elif murano_method.usage == dsl_types.MethodUsages.Static:
return (_create_instance_mpl_stub(murano_method),
_create_static_mpl_stub(murano_method))
elif murano_method.usage == dsl_types.MethodUsages.Extension:
return (_create_extension_mpl_stub(murano_method),
_create_static_mpl_stub(murano_method))
else:
raise ValueError('Unknown method usage ' + murano_method.usage)
def _create_instance_mpl_stub(murano_method):
def payload(__context, __receiver, *args, **kwargs):
executor = helpers.get_executor(__context)
return murano_method.invoke(
executor, __receiver, args, kwargs, __context, True)
fd = _create_basic_mpl_stub(murano_method, 1, payload, False)
receiver_type = dsl.MuranoObjectParameter(
weakref.proxy(murano_method.declaring_type), decorate=False)
fd.set_parameter(specs.ParameterDefinition('__receiver', receiver_type, 1))
return fd
def _create_static_mpl_stub(murano_method):
def payload(__context, __receiver, *args, **kwargs):
executor = helpers.get_executor(__context)
return murano_method.invoke(
executor, __receiver, args, kwargs, __context, True)
fd = _create_basic_mpl_stub(murano_method, 1, payload, False)
receiver_type = dsl.MuranoTypeParameter(
weakref.proxy(murano_method.declaring_type), resolve_strings=False)
fd.set_parameter(specs.ParameterDefinition('__receiver', receiver_type, 1))
return fd
def _create_extension_mpl_stub(murano_method):
def payload(__context, __receiver, *args, **kwargs):
executor = helpers.get_executor(__context)
return murano_method.invoke(
executor, murano_method.declaring_type,
(__receiver,) + args, kwargs, __context, True)
return _create_basic_mpl_stub(murano_method, 0, payload, True)
def _create_basic_mpl_stub(murano_method, reserve_params, payload,
check_first_arg):
fd = specs.FunctionDefinition(
murano_method.name, payload, is_function=False, is_method=True)
for i, (name, arg_spec) in enumerate(
six.iteritems(murano_method.arguments_scheme), 2):
six.iteritems(murano_method.arguments_scheme), reserve_params + 1):
p = specs.ParameterDefinition(
name, ContractedValue(arg_spec),
name, ContractedValue(arg_spec, with_check=check_first_arg),
position=i, default=dsl.NO_VALUE)
check_first_arg = False
fd.parameters[name] = p
fd.set_parameter(specs.ParameterDefinition(
'__context', yaqltypes.Context(), 0))
receiver_type = dsl.MuranoObjectParameter(
weakref.proxy(murano_method.declaring_type), decorate=False)
if murano_method.is_static:
receiver_type = yaqltypes.AnyOf(dsl.MuranoTypeParameter(
weakref.proxy(murano_method.declaring_type)), receiver_type)
fd.set_parameter(specs.ParameterDefinition('__receiver', receiver_type, 1))
fd.meta[constants.META_MURANO_METHOD] = murano_method
return fd
@ -273,6 +356,8 @@ def get_class_factory_definition(cls, murano_class):
args = tuple(dsl.to_mutable(arg, engine) for arg in args)
kwargs = dsl.to_mutable(kwargs, engine)
with helpers.contextual(__context):
__context[constants.CTX_NAMES_SCOPE] = \
murano_class
return helpers.evaluate(cls(*args, **kwargs), __context)
if '__init__' in cls.__dict__:

View File

@ -111,7 +111,7 @@ def inject_method_with_str(context, target, target_method,
original_class = target.type
original_function = original_class.find_single_method(target_method)
result_fd = original_function.yaql_function_definition.clone()
result_fd = original_function.instance_stub.clone()
def payload_adapter(__context, __sender, *args, **kwargs):
executor = helpers.get_executor(__context)
@ -134,7 +134,7 @@ def inject_method_with_yaql_expr(context, target, target_method, expr):
original_class = target.type
original_function = original_class.find_single_method(target_method)
result_fd = original_function.yaql_function_definition.clone()
result_fd = original_function.instance_stub.clone()
def payload_adapter(__super, __context, __sender, *args, **kwargs):
new_context = context.create_child_context()

View File

@ -93,7 +93,7 @@ class TestPackageLoader(package_loader.MuranoPackageLoader):
last_ns = {}
for data in data_lst:
last_ns = data.get('Namespaces', last_ns)
last_ns = data.get('Namespaces', last_ns.copy())
if 'Name' not in data:
continue

View File

@ -0,0 +1,122 @@
Namespaces:
=: extcls
--- # ------------------------------------------------------------------ # ---
Name: Extended
Properties:
prop:
Contract: $.int()
Default: 123
Methods:
method:
Body:
Return: $.prop
--- # ------------------------------------------------------------------ # ---
Name: Extender
Methods:
importedExtensionMethod:
Usage: Extension
Arguments:
- obj:
Contract: $.class(Extended).notNull()
- n:
Contract: $.int().notNull()
Body:
Return: [$obj.prop * $n, $obj.method() * $n]
nullableExtension:
Usage: Extension
Arguments:
- obj:
Contract: $.class(Extended)
Body:
Return: $obj?.prop
extensionMethod:
Usage: Extension
Arguments:
- obj:
Contract: $.class(Extended).notNull()
Body:
Return: 222
toTileCase:
Usage: Extension
Arguments:
- str:
Contract: $.string().notNull()
Body:
Return: join($str.toCharArray().select(
selectCase($.toLower() = $).switchCase($.toUpper(), $.toLower())), '')
--- # ------------------------------------------------------------------ # ---
Name: TestClass
Import: Extender
Methods:
testSelfExtensionMethod:
Body:
Return: new(Extended).selfExtensionMethod()
testImportedExtensionMethod:
Body:
Return: new(Extended).importedExtensionMethod(2)
testNullableExtensionMethod:
Body:
Return:
- new(Extended).nullableExtension()
- null.nullableExtension()
testExtensionsPrecedence:
Body:
Return: new(Extended).extensionMethod()
testCallOnPrimitiveTypes:
Body:
Return: QwertY.toTileCase()
testCallExtensionExplicitly:
Body:
Return: :Extender.extensionMethod(new(:Extended))
testExplicitCallDoenstWorkOnInstance:
Body:
Return: new(Extended).extensionMethod(new(Extended))
testCallPythonExtension:
Body:
Return: 4.pythonExtension()
testCallPythonExtensionExplicitly:
Body:
Return: :Extender.pythonExtension(5)
testCallPythonClassmethodExtension:
Body:
Return: 7.pythonExtension2()
selfExtensionMethod:
Usage: Extension
Arguments:
- obj:
Contract: $.class(Extended).notNull()
Body:
Return: [$obj.prop, $obj.method()]
extensionMethod:
Usage: Extension
Arguments:
- obj:
Contract: $.class(Extended).notNull()
Body:
Return: 111

View File

@ -0,0 +1,82 @@
# Copyright (c) 2016 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
from yaql.language import specs
from yaql.language import yaqltypes
from murano.dsl import dsl
from murano.tests.unit.dsl.foundation import object_model as om
from murano.tests.unit.dsl.foundation import test_case
class TestExtensionMethods(test_case.DslTestCase):
def setUp(self):
@dsl.name('extcls.Extender')
class PythonClass(object):
def __init__(self, arg):
self.value = arg
@staticmethod
@specs.meta('Usage', 'Extension')
@specs.parameter('arg', yaqltypes.Integer())
def python_extension(arg):
return arg * arg
@classmethod
@specs.meta('Usage', 'Extension')
@specs.parameter('arg', yaqltypes.Integer())
def python_extension2(cls, arg):
return cls(2 * arg).value
super(TestExtensionMethods, self).setUp()
self.package_loader.load_class_package(
'extcls.Extender', None).register_class(PythonClass)
self._runner = self.new_runner(om.Object('extcls.TestClass'))
def test_call_self_extension_method(self):
self.assertEqual([123, 123], self._runner.testSelfExtensionMethod())
def test_call_imported_extension_method(self):
self.assertEqual(
[246, 246], self._runner.testImportedExtensionMethod())
def test_call_nullable_extension_method(self):
self.assertEqual(
[123, None], self._runner.testNullableExtensionMethod())
def test_extensions_precedence(self):
self.assertEqual(111, self._runner.testExtensionsPrecedence())
def test_explicit_call(self):
self.assertEqual(222, self._runner.testCallExtensionExplicitly())
def test_explicit_call_on_instance_fails(self):
self.assertRaises(
exceptions.NoMatchingMethodException,
self._runner.testExplicitCallDoenstWorkOnInstance)
def test_call_on_primitive_types(self):
self.assertEqual('qWERTy', self._runner.testCallOnPrimitiveTypes())
def test_call_python_extension(self):
self.assertEqual(16, self._runner.testCallPythonExtension())
def test_call_python_extension_explicitly(self):
self.assertEqual(25, self._runner.testCallPythonExtensionExplicitly())
def test_call_python_classmethod_extension(self):
self.assertEqual(14, self._runner.testCallPythonClassmethodExtension())

View File

@ -0,0 +1,14 @@
---
features:
- >
New method type: extension methods. Extension methods enable you to "add"
methods to existing types without modifying the original type.
Extension methods are a special kind of static method, but they are called
as if they were instance methods on the extended type.
Extension methods are identified by "Usage: Extension" and the type
they extend is determined by their first argument contract. Thus
such methods must have at lease one parameter.
- >
New type-level keyword "Import" which can be either list or scalar
that specifies type names which extensions methods should be imported
into class context and thus become available to type members.