Support for static methods/properties

Both properties and methods can be marked as Usage: Static

Statics can be accessed using ns:Class.property / ns:Class.method(),
:Class.property / :Class.method() to access class from current namespace
or type('full.name').property / type('full.name').method() to use full type name.

In static method $ / $this are referencing current class rather than object.
Static properties are not loaded from object model.

Also methods of io.murano.configuration.Linux class are now static.
Since static methods can be called on the instance it doesn't break
backward compatibility.

Implements blueprint: muranopl-statics
Change-Id: Ic7c6beed9222f4bca118877a60fdabfdd9d65e5a
This commit is contained in:
Stan Lagun 2016-01-25 02:52:15 +03:00
parent f4ff2fb482
commit 94c904e1cb
24 changed files with 641 additions and 103 deletions

View File

@ -19,6 +19,7 @@ Name: Linux
Methods: Methods:
runCommand: runCommand:
Usage: Static
Arguments: Arguments:
- agent: - agent:
Contract: $.class(sys:Agent) Contract: $.class(sys:Agent)
@ -54,6 +55,7 @@ Methods:
- Return: $agent.call($template, $resources) - Return: $agent.call($template, $resources)
putFile: putFile:
Usage: Static
Arguments: Arguments:
- agent: - agent:
Contract: $.class(sys:Agent) Contract: $.class(sys:Agent)

View File

@ -233,21 +233,22 @@ class MuranoTestRunner(object):
for m in test_cases: for m in test_cases:
# Create new executor for each test case to provide # Create new executor for each test case to provide
# pure test environment # pure test environment
executer = executor.MuranoDslExecutor( dsl_executor = executor.MuranoDslExecutor(
pkg_loader, pkg_loader,
mock_context_manager.MockContextManager(), mock_context_manager.MockContextManager(),
test_env) test_env)
obj = package.find_class(pkg_class, False).new( obj = package.find_class(pkg_class, False).new(
None, executer.object_store)(None) None, dsl_executor.object_store, dsl_executor)(None)
self._call_service_method('setUp', executer, obj) self._call_service_method('setUp', dsl_executor, obj)
obj.type.methods[m].usage = 'Action' obj.type.methods[m].usage = 'Action'
test_env.start() test_env.start()
try: try:
obj.type.invoke(m, executer, obj, (), {}) obj.type.invoke(m, dsl_executor, obj, (), {})
LOG.debug('\n.....{0}.{1}.....OK'.format(obj.type.name, LOG.debug('\n.....{0}.{1}.....OK'.format(obj.type.name,
m)) m))
self._call_service_method('tearDown', executer, obj) self._call_service_method(
'tearDown', dsl_executor, obj)
except Exception: except Exception:
LOG.exception('\n.....{0}.{1}.....FAILURE\n' LOG.exception('\n.....{0}.{1}.....FAILURE\n'
''.format(obj.type.name, m)) ''.format(obj.type.name, m))

View File

@ -109,9 +109,9 @@ class MuranoTypeName(yaqltypes.PythonType):
if function_spec.meta.get(constants.META_MURANO_METHOD): if function_spec.meta.get(constants.META_MURANO_METHOD):
context = helpers.get_caller_context(context) context = helpers.get_caller_context(context)
murano_type = helpers.get_type(context) murano_type = helpers.get_type(context)
value = dsl_types.MuranoTypeReference(helpers.get_class( value = helpers.get_class(
murano_type.namespace_resolver.resolve_name(value), murano_type.namespace_resolver.resolve_name(value),
context)) context).get_reference()
return value return value

View File

@ -45,8 +45,16 @@ class MuranoTypeReference(object):
def murano_class(self): def murano_class(self):
return self.__murano_class return self.__murano_class
def __str__(self): def __repr__(self):
return self.__murano_class.name return '*' + repr(self.murano_class)
def __eq__(self, other):
if not isinstance(other, MuranoTypeReference):
return False
return self.murano_class == other.murano_class
def __hash__(self):
return hash(self.murano_class)
class YaqlExpression(object): class YaqlExpression(object):

View File

@ -160,3 +160,9 @@ class UninitializedPropertyAccessError(PropertyAccessError):
class CircularExpressionDependenciesError(Exception): class CircularExpressionDependenciesError(Exception):
pass pass
class InvalidLhsTargetError(Exception):
def __init__(self, target):
super(InvalidLhsTargetError, self).__init__(
'Invalid assignment target "%s"' % target)

View File

@ -28,6 +28,7 @@ from murano.common.i18n import _LW
from murano.dsl import attribute_store from murano.dsl import attribute_store
from murano.dsl import constants from murano.dsl import constants
from murano.dsl import dsl from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import helpers from murano.dsl import helpers
from murano.dsl import murano_method from murano.dsl import murano_method
from murano.dsl import object_store from murano.dsl import object_store
@ -85,7 +86,9 @@ class MuranoDslExecutor(object):
context = self.create_method_context( context = self.create_method_context(
self.create_object_context(this, context), method) self.create_object_context(this, context), method)
this = this.real_this
if isinstance(this, dsl_types.MuranoObject):
this = this.real_this
if method.arguments_scheme is not None: if method.arguments_scheme is not None:
args, kwargs = self._canonize_parameters( args, kwargs = self._canonize_parameters(
@ -119,7 +122,10 @@ class MuranoDslExecutor(object):
@contextlib.contextmanager @contextlib.contextmanager
def _acquire_method_lock(self, func, this): def _acquire_method_lock(self, func, this):
method_id = id(func) method_id = id(func)
this_id = this.object_id if isinstance(this, dsl_types.MuranoClass):
this_id = id(this)
else:
this_id = this.object_id
thread_id = helpers.get_current_thread_id() thread_id = helpers.get_current_thread_id()
while True: while True:
event, event_owner = self._locks.get( event, event_owner = self._locks.get(
@ -262,13 +268,24 @@ class MuranoDslExecutor(object):
return context return context
def create_object_context(self, obj, caller_context=None): def create_object_context(self, obj, caller_context=None):
class_context = self.create_class_context(obj.type) if isinstance(obj, dsl_types.MuranoClass):
context = helpers.link_contexts( obj_type = obj
class_context, self.context_manager.create_object_context( obj = None
obj)).create_child_context() else:
context[constants.CTX_THIS] = obj.real_this obj_type = obj.type
context['this'] = obj.real_this class_context = self.create_class_context(obj_type)
context[''] = obj.real_this if obj is not None:
context = helpers.link_contexts(
class_context, self.context_manager.create_object_context(
obj)).create_child_context()
context[constants.CTX_THIS] = obj.real_this
context['this'] = obj.real_this
context[''] = obj.real_this
else:
context = class_context.create_child_context()
type_ref = obj_type.get_reference()
context['this'] = type_ref
context[''] = type_ref
if caller_context is not None: if caller_context is not None:
caller = caller_context caller = caller_context

View File

@ -153,7 +153,9 @@ def get_environment(context=None):
def get_object_store(context=None): def get_object_store(context=None):
context = context or get_context() context = context or get_context()
return context[constants.CTX_THIS].object_store this = context[constants.CTX_THIS]
return this.object_store if isinstance(
this, dsl_types.MuranoObject) else None
def get_package_loader(context=None): def get_package_loader(context=None):

View File

@ -20,8 +20,10 @@ from yaql.language import utils
from yaql.language import yaqltypes from yaql.language import yaqltypes
from murano.dsl import constants from murano.dsl import constants
from murano.dsl import dsl
from murano.dsl import dsl_types from murano.dsl import dsl_types
from murano.dsl import exceptions from murano.dsl import exceptions
from murano.dsl import yaql_functions
from murano.dsl import yaql_integration from murano.dsl import yaql_integration
@ -68,6 +70,14 @@ class LhsExpression(object):
((key, value),)))) ((key, value),))))
elif isinstance(src, dsl_types.MuranoObject): elif isinstance(src, dsl_types.MuranoObject):
src.set_property(key, value, root_context) src.set_property(key, value, root_context)
elif isinstance(src, (
dsl_types.MuranoTypeReference,
dsl_types.MuranoClass)):
if not isinstance(src, dsl_types.MuranoClass):
mc = src.murano_class
else:
mc = src
mc.set_property(key, value, root_context)
else: else:
raise ValueError( raise ValueError(
'attribution may only be applied to ' 'attribution may only be applied to '
@ -118,16 +128,51 @@ class LhsExpression(object):
else: else:
return attribution(this, index) return attribution(this, index)
def _wrap_type_reference(tr):
return LhsExpression.Property(lambda: tr, self._invalid_target)
@specs.parameter('prefix', yaqltypes.Keyword())
@specs.parameter('name', yaqltypes.Keyword())
@specs.name('#operator_:')
def ns_resolve(prefix, name):
return _wrap_type_reference(
yaql_functions.ns_resolve(context, prefix, name))
@specs.parameter('name', yaqltypes.Keyword())
@specs.name('#unary_operator_:')
def ns_resolve_unary(context, name):
return _wrap_type_reference(
yaql_functions.ns_resolve_unary(context, name))
@specs.parameter('object_', dsl_types.MuranoObject)
def type_(object_):
return _wrap_type_reference(yaql_functions.type_(object_))
@specs.name('type')
@specs.parameter('cls', dsl.MuranoTypeName())
def type_from_name(cls):
return _wrap_type_reference(cls)
context = yaql_integration.create_empty_context() context = yaql_integration.create_empty_context()
context.register_function(get_context_data, '#get_context_data') context.register_function(get_context_data, '#get_context_data')
context.register_function(attribution, '#operator_.') context.register_function(attribution, '#operator_.')
context.register_function(indexation, '#indexer') context.register_function(indexation, '#indexer')
context.register_function(ns_resolve)
context.register_function(ns_resolve_unary)
context.register_function(type_)
context.register_function(type_from_name)
return context return context
def _invalid_target(self, *args, **kwargs):
raise exceptions.InvalidLhsTargetError(self._expression)
def __call__(self, value, context): def __call__(self, value, context):
new_context = self._create_context(context) new_context = self._create_context(context)
new_context[''] = context['$'] new_context[''] = context['$']
new_context[constants.CTX_TYPE] = context[constants.CTX_TYPE]
self._current_obj = None self._current_obj = None
self._current_obj_name = None self._current_obj_name = None
property = self._expression(context=new_context) property = self._expression(context=new_context)
if not isinstance(property, LhsExpression.Property):
self._invalid_target()
property.set(value) property.set(value)

View File

@ -46,6 +46,7 @@ class MuranoClass(dsl_types.MuranoClass):
package.find_class(constants.CORE_LIBRARY_OBJECT)] package.find_class(constants.CORE_LIBRARY_OBJECT)]
self._context = None self._context = None
self._parent_mappings = self._build_parent_remappings() self._parent_mappings = self._build_parent_remappings()
self._property_values = {}
@classmethod @classmethod
def create(cls, data, package, name=None): def create(cls, data, package, name=None):
@ -53,7 +54,7 @@ class MuranoClass(dsl_types.MuranoClass):
ns_resolver = namespace_resolver.NamespaceResolver(namespaces) ns_resolver = namespace_resolver.NamespaceResolver(namespaces)
if not name: if not name:
name = ns_resolver.resolve_name(data['Name']) name = ns_resolver.resolve_name(str(data['Name']))
parent_class_names = data.get('Extends') parent_class_names = data.get('Extends')
parent_classes = [] parent_classes = []
@ -61,7 +62,7 @@ class MuranoClass(dsl_types.MuranoClass):
if not utils.is_sequence(parent_class_names): if not utils.is_sequence(parent_class_names):
parent_class_names = [parent_class_names] parent_class_names = [parent_class_names]
for parent_name in parent_class_names: for parent_name in parent_class_names:
full_name = ns_resolver.resolve_name(parent_name) full_name = ns_resolver.resolve_name(str(parent_name))
parent_classes.append(package.find_class(full_name)) parent_classes.append(package.find_class(full_name))
type_obj = cls(ns_resolver, name, package, parent_classes) type_obj = cls(ns_resolver, name, package, parent_classes)
@ -189,6 +190,19 @@ class MuranoClass(dsl_types.MuranoClass):
return self._choose_symbol( return self._choose_symbol(
lambda cls: cls.properties.get(name)) lambda cls: cls.properties.get(name))
def find_static_property(self, name):
def prop_func(cls):
prop = cls.properties.get(name)
if prop is not None and prop.usage == 'Static':
return prop
result = self._choose_symbol(prop_func)
if len(result) < 1:
raise exceptions.NoPropertyFound(name)
elif len(result) > 1:
raise exceptions.AmbiguousPropertyNameError(name)
return result[0]
def find_single_method(self, name): def find_single_method(self, name):
result = self.find_method(name) result = self.find_method(name)
if len(result) < 1: if len(result) < 1:
@ -242,12 +256,13 @@ class MuranoClass(dsl_types.MuranoClass):
return True return True
return any(cls is self for cls in obj.ancestors()) return any(cls is self for cls in obj.ancestors())
def new(self, owner, object_store, **kwargs): def new(self, owner, object_store, executor, **kwargs):
obj = murano_object.MuranoObject(self, owner, object_store, **kwargs) obj = murano_object.MuranoObject(
self, owner, object_store, executor, **kwargs)
def initializer(__context, **params): def initializer(__context, **params):
if __context is None: if __context is None:
__context = object_store.executor.create_object_context(obj) __context = executor.create_object_context(obj)
init_context = __context.create_child_context() init_context = __context.create_child_context()
init_context[constants.CTX_ALLOW_PROPERTY_WRITES] = True init_context[constants.CTX_ALLOW_PROPERTY_WRITES] = True
obj.initialize(init_context, object_store, params) obj.initialize(init_context, object_store, params)
@ -359,3 +374,17 @@ class MuranoClass(dsl_types.MuranoClass):
m.yaql_function_definition, m.yaql_function_definition,
name=m.yaql_function_definition.name) name=m.yaql_function_definition.name)
return self._context return self._context
def get_property(self, name, context):
prop = self.find_static_property(name)
cls = prop.murano_class
value = cls._property_values.get(name, prop.default)
return prop.validate(value, cls, None, context)
def set_property(self, name, value, context):
prop = self.find_static_property(name)
cls = prop.murano_class
cls._property_values[name] = prop.validate(value, cls, None, context)
def get_reference(self):
return dsl_types.MuranoTypeReference(self)

View File

@ -35,7 +35,8 @@ virtual_exceptions.register()
class MethodUsages(object): class MethodUsages(object):
Action = 'Action' Action = 'Action'
Runtime = 'Runtime' Runtime = 'Runtime'
All = set([Action, Runtime]) Static = 'Static'
All = set([Action, Runtime, Static])
class MuranoMethod(dsl_types.MuranoMethod): class MuranoMethod(dsl_types.MuranoMethod):
@ -111,13 +112,18 @@ class MuranoMethod(dsl_types.MuranoMethod):
def invoke(self, executor, this, args, kwargs, context=None, def invoke(self, executor, this, args, kwargs, context=None,
skip_stub=False): skip_stub=False):
if not self.murano_class.is_compatible(this): if self.usage == 'Static':
this = None
elif not self.murano_class.is_compatible(this):
raise Exception("'this' must be of compatible type") raise Exception("'this' must be of compatible type")
if isinstance(this, dsl.MuranoObjectInterface): if isinstance(this, dsl.MuranoObjectInterface):
this = this.object this = this.object
if this is not None:
this = this.cast(self.murano_class)
else:
this = self.murano_class
return executor.invoke_method( return executor.invoke_method(
self, this.cast(self.murano_class), self, this, context, args, kwargs, skip_stub)
context, args, kwargs, skip_stub)
class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec): class MuranoMethodArgument(dsl_types.MuranoMethodArgument, typespec.Spec):

View File

@ -26,8 +26,9 @@ from murano.dsl import yaql_integration
class MuranoObject(dsl_types.MuranoObject): class MuranoObject(dsl_types.MuranoObject):
def __init__(self, murano_class, owner, object_store, object_id=None, def __init__(self, murano_class, owner, object_store, executor,
name=None, known_classes=None, defaults=None, this=None): object_id=None, name=None, known_classes=None,
defaults=None, this=None):
if known_classes is None: if known_classes is None:
known_classes = {} known_classes = {}
self.__owner = owner.real_this if owner else None self.__owner = owner.real_this if owner else None
@ -39,7 +40,9 @@ class MuranoObject(dsl_types.MuranoObject):
self.__this = this self.__this = this
self.__name = name self.__name = name
self.__extension = None self.__extension = None
self.__object_store = weakref.ref(object_store) self.__object_store = \
None if object_store is None else weakref.ref(object_store)
self.__executor = weakref.ref(executor)
self.__config = murano_class.package.get_class_config( self.__config = murano_class.package.get_class_config(
murano_class.name) murano_class.name)
if not isinstance(self.__config, dict): if not isinstance(self.__config, dict):
@ -49,7 +52,7 @@ class MuranoObject(dsl_types.MuranoObject):
name = parent_class.name name = parent_class.name
if name not in known_classes: if name not in known_classes:
obj = parent_class.new( obj = parent_class.new(
owner, object_store, object_id=self.__object_id, owner, object_store, executor, object_id=self.__object_id,
known_classes=known_classes, defaults=defaults, known_classes=known_classes, defaults=defaults,
this=self.real_this).object this=self.real_this).object
@ -72,7 +75,11 @@ class MuranoObject(dsl_types.MuranoObject):
@property @property
def object_store(self): def object_store(self):
return self.__object_store() return None if self.__object_store is None else self.__object_store()
@property
def executor(self):
return self.__executor()
def initialize(self, context, object_store, params): def initialize(self, context, object_store, params):
if self.__initialized: if self.__initialized:
@ -105,7 +112,8 @@ class MuranoObject(dsl_types.MuranoObject):
if property_name in used_names: if property_name in used_names:
continue continue
if spec.usage == typespec.PropertyUsages.Config: if spec.usage in (typespec.PropertyUsages.Config,
typespec.PropertyUsages.Static):
used_names.add(property_name) used_names.add(property_name)
continue continue
if spec.usage == typespec.PropertyUsages.Runtime: if spec.usage == typespec.PropertyUsages.Runtime:
@ -133,7 +141,8 @@ class MuranoObject(dsl_types.MuranoObject):
last_errors = errors last_errors = errors
executor = helpers.get_executor(context) executor = helpers.get_executor(context)
if not object_store.initializing and self.__extension is None: if ((object_store is None or not object_store.initializing) and
self.__extension is None):
method = self.type.methods.get('__init__') method = self.type.methods.get('__init__')
if method: if method:
filtered_params = yaql_integration.filter_parameters( filtered_params = yaql_integration.filter_parameters(
@ -146,7 +155,7 @@ class MuranoObject(dsl_types.MuranoObject):
for parent in self.__parents.values(): for parent in self.__parents.values():
parent.initialize(context, object_store, params) parent.initialize(context, object_store, params)
if not object_store.initializing and init: if (object_store is None or not object_store.initializing) and init:
context[constants.CTX_ARGUMENT_OWNER] = self.real_this context[constants.CTX_ARGUMENT_OWNER] = self.real_this
init.invoke(executor, self.real_this, (), init_args, context) init.invoke(executor, self.real_this, (), init_args, context)
self.__initialized = True self.__initialized = True
@ -173,11 +182,18 @@ class MuranoObject(dsl_types.MuranoObject):
if caller_class is not None and caller_class.is_compatible(self): if caller_class is not None and caller_class.is_compatible(self):
start_type, derived = caller_class, True start_type, derived = caller_class, True
if name in start_type.properties: if name in start_type.properties:
return self.cast(start_type)._get_property_value(name) spec = start_type.properties[name]
if spec.usage == typespec.PropertyUsages.Static:
return spec.murano_class.get_property(name, context)
else:
return self.cast(start_type)._get_property_value(name)
else: else:
try: try:
spec = start_type.find_single_property(name) spec = start_type.find_single_property(name)
return self.cast(spec.murano_class).__properties[name] if spec.usage == typespec.PropertyUsages.Static:
return spec.murano_class.get_property(name, context)
else:
return self.cast(spec.murano_class).__properties[name]
except exceptions.NoPropertyFound: except exceptions.NoPropertyFound:
if derived: if derived:
return self.cast(caller_class)._get_property_value(name) return self.cast(caller_class)._get_property_value(name)
@ -199,11 +215,12 @@ class MuranoObject(dsl_types.MuranoObject):
declared_properties = start_type.find_properties( declared_properties = start_type.find_properties(
lambda p: p.name == name) lambda p: p.name == name)
if context is None: if context is None:
context = self.object_store.executor.create_object_context(self) context = self.executor.create_object_context(self)
if len(declared_properties) > 0: if len(declared_properties) > 0:
declared_properties = self.type.find_properties( declared_properties = self.type.find_properties(
lambda p: p.name == name) lambda p: p.name == name)
values_to_assign = [] values_to_assign = []
classes_for_static_properties = []
for spec in declared_properties: for spec in declared_properties:
if (caller_class is not None and not if (caller_class is not None and not
helpers.are_property_modifications_allowed(context) and helpers.are_property_modifications_allowed(context) and
@ -211,16 +228,21 @@ class MuranoObject(dsl_types.MuranoObject):
not derived)): not derived)):
raise exceptions.NoWriteAccessError(name) raise exceptions.NoWriteAccessError(name)
default = self.__config.get(name, spec.default) if spec.usage == typespec.PropertyUsages.Static:
default = self.__defaults.get(name, default) classes_for_static_properties.append(spec.murano_class)
default = helpers.evaluate(default, context) else:
default = self.__config.get(name, spec.default)
default = self.__defaults.get(name, default)
default = helpers.evaluate(default, context)
obj = self.cast(spec.murano_class) obj = self.cast(spec.murano_class)
values_to_assign.append((obj, spec.validate( values_to_assign.append((obj, spec.validate(
value, self.real_this, value, self.real_this,
self.real_this, default=default))) self.real_this, context, default=default)))
for obj, value in values_to_assign: for obj, value in values_to_assign:
obj.__properties[name] = value obj.__properties[name] = value
for cls in classes_for_static_properties:
cls.set_property(name, value, context)
elif derived: elif derived:
obj = self.cast(caller_class) obj = self.cast(caller_class)
obj.__properties[name] = value obj.__properties[name] = value

View File

@ -12,26 +12,42 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import re
TYPE_NAME_RE = re.compile(r'^([a-zA-Z_]\w*:|:)?[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*$')
NS_RE = re.compile(r'^([a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*)?$')
PREFIX_RE = re.compile(r'^([a-zA-Z_]\w*|=)$')
class NamespaceResolver(object): class NamespaceResolver(object):
def __init__(self, namespaces): def __init__(self, namespaces):
self._namespaces = namespaces for prefix, ns in namespaces.items():
if PREFIX_RE.match(prefix) is None:
raise ValueError(
'Invalid namespace prefix "{0}"'.format(prefix))
if NS_RE.match(ns) is None:
raise ValueError('Invalid namespace "{0}"'.format(ns))
self._namespaces = namespaces.copy()
self._namespaces.setdefault('=', '')
self._namespaces[''] = '' self._namespaces[''] = ''
def resolve_name(self, name, relative=None): def resolve_name(self, name):
if name is None: if name is None or TYPE_NAME_RE.match(name) is None:
raise ValueError() raise ValueError('Invalid type name "{0}"'.format(name))
if name and name.startswith(':'): if ':' not in name:
return name[1:] if '.' in name:
if ':' in name: parts = ['', name]
else:
parts = ['=', name]
else:
parts = name.split(':') parts = name.split(':')
if len(parts) != 2 or not parts[1]: if not parts[0]:
raise NameError('Incorrectly formatted name ' + name) parts[0] = '='
if parts[0] not in self._namespaces:
raise KeyError('Unknown namespace prefix ' + parts[0]) if parts[0] not in self._namespaces:
return '.'.join((self._namespaces[parts[0]], parts[1])) raise KeyError('Unknown namespace prefix ' + parts[0])
if not relative and '=' in self._namespaces and '.' not in name:
return '.'.join((self._namespaces['='], name)) ns = self._namespaces[parts[0]]
if relative and '.' not in name: if not ns:
return '.'.join((relative, name)) return name
return name return '.'.join((ns, parts[1]))

View File

@ -81,7 +81,7 @@ class ObjectStore(object):
return factory return factory
else: else:
factory = class_obj.new( factory = class_obj.new(
owner, self, owner, self, self.executor,
name=system_key.get('name'), name=system_key.get('name'),
object_id=object_id, defaults=defaults) object_id=object_id, defaults=defaults)
self._store[object_id] = factory self._store[object_id] = factory

View File

@ -157,6 +157,12 @@ def argument_owner(method_argument):
return method_argument.murano_method return method_argument.murano_method
@specs.yaql_property(dsl_types.MuranoClass)
@specs.name('type')
def type_to_type_ref(murano_class):
return murano_class.get_reference()
def register(context): def register(context):
funcs = ( funcs = (
class_name, methods, properties, ancestors, package, class_version, class_name, methods, properties, ancestors, package, class_version,
@ -164,7 +170,8 @@ def register(context):
property_usage, property_usage,
method_name, arguments, method_owner, method_name, arguments, method_owner,
types, package_name, package_version, types, package_name, package_version,
argument_name, argument_has_default, argument_owner argument_name, argument_has_default, argument_owner,
type_to_type_ref
) )
for f in funcs: for f in funcs:
context.register_function(f) context.register_function(f)

View File

@ -14,7 +14,9 @@
import weakref import weakref
from murano.dsl import dsl_types
from murano.dsl import exceptions from murano.dsl import exceptions
from murano.dsl import helpers
from murano.dsl import type_scheme from murano.dsl import type_scheme
@ -25,8 +27,9 @@ class PropertyUsages(object):
Runtime = 'Runtime' Runtime = 'Runtime'
Const = 'Const' Const = 'Const'
Config = 'Config' Config = 'Config'
All = set([In, Out, InOut, Runtime, Const, Config]) Static = 'Static'
Writable = set([Out, InOut, Runtime]) All = set([In, Out, InOut, Runtime, Const, Config, Static])
Writable = set([Out, InOut, Runtime, Static])
class Spec(object): class Spec(object):
@ -41,13 +44,19 @@ class Spec(object):
'Unknown type {0}. Must be one of ({1})'.format( 'Unknown type {0}. Must be one of ({1})'.format(
self._usage, ', '.join(PropertyUsages.All))) self._usage, ', '.join(PropertyUsages.All)))
def validate(self, value, this, owner, default=None): def validate(self, value, this, owner, context, default=None):
if default is None: if default is None:
default = self.default default = self.default
return self._contract( executor = helpers.get_executor(context)
value, this.object_store.executor.create_object_context( if isinstance(this, dsl_types.MuranoClass):
this.cast(self._container_class())), return self._contract(
this, owner, default) value, executor.create_object_context(this),
None, None, default)
else:
return self._contract(
value, executor.create_object_context(
this.cast(self._container_class())),
this, owner, default)
@property @property
def default(self): def default(self):

View File

@ -68,7 +68,7 @@ class YaqlExpression(dsl_types.YaqlExpression):
def is_expression(expression, version): def is_expression(expression, version):
if not isinstance(expression, six.string_types): if not isinstance(expression, six.string_types):
return False return False
if re.match('^[\s\w\d.:]*$', expression): if re.match('^[\s\w\d.]*$', expression):
return False return False
try: try:
yaql_integration.parse(expression, version) yaql_integration.parse(expression, version)

View File

@ -46,12 +46,14 @@ def cast(context, object_, type__, version_spec=None):
def new(__context, __type_name, __owner=None, __object_name=None, __extra=None, def new(__context, __type_name, __owner=None, __object_name=None, __extra=None,
**parameters): **parameters):
object_store = helpers.get_object_store(__context) object_store = helpers.get_object_store(__context)
executor = helpers.get_executor(__context)
new_context = __context.create_child_context() new_context = __context.create_child_context()
for key, value in six.iteritems(parameters): for key, value in six.iteritems(parameters):
if utils.is_keyword(key): if utils.is_keyword(key):
new_context[key] = value new_context[key] = value
return __type_name.murano_class.new( return __type_name.murano_class.new(
__owner, object_store, name=__object_name)(new_context, **parameters) __owner, object_store, executor, name=__object_name)(
new_context, **parameters)
@specs.parameter('type_name', dsl.MuranoTypeName()) @specs.parameter('type_name', dsl.MuranoTypeName())
@ -110,14 +112,32 @@ def sleep_(seconds):
@specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) @specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True))
def type_(object_): def type_(object_):
return None if object_ is None else object_.type.get_reference()
@specs.name('type')
@specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True))
def type_legacy(object_):
return None if object_ is None else object_.type.name return None if object_ is None else object_.type.name
@specs.name('type')
@specs.parameter('cls', dsl.MuranoTypeName())
def type_from_name(cls):
return cls
@specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) @specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True))
def typeinfo(object_): def typeinfo(object_):
return None if object_ is None else object_.type return None if object_ is None else object_.type
@specs.parameter('cls', dsl.MuranoTypeName())
@specs.name('typeinfo')
def typeinfo_for_class(cls):
return cls.murano_class
@specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True)) @specs.parameter('object_', dsl.MuranoObjectParameterType(nullable=True))
def name(object_): def name(object_):
return None if object_ is None else object_.name return None if object_ is None else object_.name
@ -130,6 +150,13 @@ def obj_attribution(context, obj, property_name):
return obj.get_property(property_name, context) return obj.get_property(property_name, context)
@specs.parameter('cls', dsl_types.MuranoTypeReference)
@specs.parameter('property_name', yaqltypes.Keyword())
@specs.name('#operator_.')
def obj_attribution_static(context, cls, property_name):
return cls.murano_class.get_property(property_name, context)
@specs.parameter('receiver', dsl.MuranoObjectParameterType()) @specs.parameter('receiver', dsl.MuranoObjectParameterType())
@specs.parameter('expr', yaqltypes.Lambda(method=True)) @specs.parameter('expr', yaqltypes.Lambda(method=True))
@specs.inject('operator', yaqltypes.Super(with_context=True)) @specs.inject('operator', yaqltypes.Super(with_context=True))
@ -144,14 +171,32 @@ def op_dot(context, receiver, expr, operator):
return operator(ctx2, receiver, expr) return operator(ctx2, receiver, expr)
@specs.parameter('sender', dsl_types.MuranoTypeReference)
@specs.parameter('expr', yaqltypes.Lambda(method=True))
@specs.inject('operator', yaqltypes.Super(with_context=True))
@specs.name('#operator_.')
def op_dot_static(context, sender, expr, operator):
executor = helpers.get_executor(context)
type_context = executor.context_manager.create_class_context(
sender.murano_class)
ctx2 = helpers.link_contexts(context, type_context)
return operator(ctx2, None, expr)
@specs.parameter('prefix', yaqltypes.Keyword()) @specs.parameter('prefix', yaqltypes.Keyword())
@specs.parameter('name', yaqltypes.Keyword()) @specs.parameter('name', yaqltypes.Keyword())
@specs.name('#operator_:') @specs.name('#operator_:')
def ns_resolve(context, prefix, name): def ns_resolve(context, prefix, name):
murano_type = helpers.get_type(context) murano_type = helpers.get_type(context)
return dsl_types.MuranoTypeReference(helpers.get_class( return helpers.get_class(
murano_type.namespace_resolver.resolve_name( murano_type.namespace_resolver.resolve_name(
prefix + ':' + name), context)) prefix + ':' + name), context).get_reference()
@specs.parameter('name', yaqltypes.Keyword())
@specs.name('#unary_operator_:')
def ns_resolve_unary(context, name):
return ns_resolve(context, '', name)
@specs.parameter('obj', dsl.MuranoObjectParameterType(nullable=True)) @specs.parameter('obj', dsl.MuranoObjectParameterType(nullable=True))
@ -173,20 +218,28 @@ def register(context, runtime_version):
context.register_function(require) context.register_function(require)
context.register_function(find) context.register_function(find)
context.register_function(sleep_) context.register_function(sleep_)
context.register_function(type_)
context.register_function(typeinfo) context.register_function(typeinfo)
context.register_function(typeinfo_for_class)
context.register_function(name) context.register_function(name)
context.register_function(obj_attribution) context.register_function(obj_attribution)
context.register_function(obj_attribution_static)
context.register_function(op_dot) context.register_function(op_dot)
context.register_function(op_dot_static)
context.register_function(ns_resolve) context.register_function(ns_resolve)
context.register_function(ns_resolve_unary)
reflection.register(context) reflection.register(context)
context.register_function(is_instance_of) context.register_function(is_instance_of)
if runtime_version <= constants.RUNTIME_VERSION_1_3:
context.register_function(type_legacy)
else:
context.register_function(type_)
if runtime_version <= constants.RUNTIME_VERSION_1_1: if runtime_version <= constants.RUNTIME_VERSION_1_1:
context2 = context.create_child_context() context = context.create_child_context()
for t in ('id', 'cast', 'super', 'psuper', 'type'): for t in ('id', 'cast', 'super', 'psuper', 'type'):
for spec in utils.to_extension_method(t, context): for spec in utils.to_extension_method(t, context):
context2.register_function(spec) context.register_function(spec)
return context2
context.register_function(type_from_name)
return context return context

View File

@ -51,7 +51,10 @@ def _add_operators(engine_factory):
engine_factory.insert_operator( engine_factory.insert_operator(
'>', True, 'is', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, False) '>', True, 'is', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, False)
engine_factory.insert_operator( engine_factory.insert_operator(
'.', True, ':', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True) None, True, ':', factory.OperatorType.BINARY_LEFT_ASSOCIATIVE, True)
engine_factory.insert_operator(
':', True, ':', factory.OperatorType.PREFIX_UNARY, False)
engine_factory.operators.insert(0, ())
def _create_engine(runtime_version): def _create_engine(runtime_version):
@ -86,7 +89,7 @@ class ContractedValue(yaqltypes.GenericType):
lambda value, sender, context, *args, **kwargs: lambda value, sender, context, *args, **kwargs:
self._value_spec.validate( self._value_spec.validate(
value, sender.real_this, value, sender.real_this,
context[constants.CTX_ARGUMENT_OWNER])) context[constants.CTX_ARGUMENT_OWNER], context))
def convert(self, value, *args, **kwargs): def convert(self, value, *args, **kwargs):
if value is None: if value is None:
@ -220,8 +223,9 @@ def _build_mpl_wrapper_function_definition(murano_method):
fd.set_parameter(specs.ParameterDefinition( fd.set_parameter(specs.ParameterDefinition(
'__context', yaqltypes.Context(), 0)) '__context', yaqltypes.Context(), 0))
fd.set_parameter(specs.ParameterDefinition( nullable = murano_method.usage == 'Static'
'__sender', yaqltypes.PythonType(dsl_types.MuranoObject, False), 1)) sender_type = yaqltypes.PythonType(dsl_types.MuranoObject, nullable)
fd.set_parameter(specs.ParameterDefinition('__sender', sender_type, 1))
fd.meta[constants.META_MURANO_METHOD] = murano_method fd.meta[constants.META_MURANO_METHOD] = murano_method
return fd return fd

View File

@ -80,7 +80,9 @@ class TestPackageLoader(package_loader.MuranoPackageLoader):
def _build_index(self, directory): def _build_index(self, directory):
yamls = [os.path.join(dirpath, f) yamls = [os.path.join(dirpath, f)
for dirpath, _, files in os.walk(directory) for dirpath, _, files in os.walk(directory)
for f in fnmatch.filter(files, '*.yaml')] for f in fnmatch.filter(files, '*.yaml')
if f != 'manifest.yaml'
]
for class_def_file in yamls: for class_def_file in yamls:
self._load_class(class_def_file) self._load_class(class_def_file)

View File

@ -0,0 +1,192 @@
Namespaces:
ns: test
=: test
Name: TestStatics
Extends: TestStaticsBase
Properties:
staticProperty:
Contract: $.string()
Usage: Static
Default: xxx
conflictingStaticProperty:
Contract: $.string()
Default: 'conflictingStaticProperty-child'
Usage: Static
instanceProperty:
Contract: $.string()
Default: instanceProperty
staticProperty2:
Contract: $.string()
Default: staticProperty
Usage: Static
Methods:
testStaticTest:
Usage: Static
Body:
Return: $
testCallStaticMethodOnObject:
Body:
Return: $.simpleStaticMethod()
testCallStaticMethodOnClassName:
Body:
Return: :TestStatics.simpleStaticMethod()
testCallStaticMethodOnClassNameWithNs:
Body:
Return: ns:TestStatics.simpleStaticMethod()
testCallStaticMethodFromAnotherMethod:
Body:
Return: ns:TestStatics.simpleStaticMethod2()
testStaticThis:
Body:
Return: $.returnStaticThis()
testNoAccessToInstanceProperties:
Body:
Return: $.accessInstanceProperty()
testAccessStaticPropertyFromInstanceMethod:
Body:
Return: $.staticProperty
testAccessStaticPropertyFromStaticMethod:
Body:
Return: $.accessStaticProperty()
simpleStaticMethod:
Usage: Static
Body:
Return: 123
simpleStaticMethod2:
Usage: Static
Body:
Return: $.simpleStaticMethod() +
$this.simpleStaticMethod() +
ns:TestStatics.simpleStaticMethod() +
:TestStatics.simpleStaticMethod() +
type('test.TestStatics').simpleStaticMethod()
returnStaticThis:
Usage: Static
Body:
Return: $
accessInstanceProperty:
Usage: Static
Body:
Return: $.instanceProperty
accessStaticProperty:
Usage: Static
Body:
Return: $.staticProperty
testModifyStaticPropertyUsingDollar:
Body:
Return: $.modifyStaticPropertyUsingDollar()
modifyStaticPropertyUsingDollar:
Usage: Static
Body:
- $.staticProperty: qq
- Return: $.staticProperty
testModifyStaticPropertyUsingThis:
Body:
Return: $.modifyStaticPropertyUsingThis()
modifyStaticPropertyUsingThis:
Usage: Static
Body:
- $this.staticProperty: qq
- Return: $this.staticProperty
testModifyStaticPropertyUsingClassName:
Body:
Return: $.modifyStaticPropertyUsingClassName()
modifyStaticPropertyUsingClassName:
Usage: Static
Body:
- :TestStatics.staticProperty: qq
- Return: :TestStatics.staticProperty
testModifyStaticPropertyUsingNsClassName:
Body:
Return: $.modifyStaticPropertyUsingNsClassName()
modifyStaticPropertyUsingNsClassName:
Usage: Static
Body:
- ns:TestStatics.staticProperty: qq
- Return: ns:TestStatics.staticProperty
testModifyStaticPropertyUsingTypeFunc:
Body:
Return: $.modifyStaticPropertyUsingTypeFunc()
modifyStaticPropertyUsingTypeFunc:
Usage: Static
Body:
- type('test.TestStatics').staticProperty: qq
- Return: type('test.TestStatics').staticProperty
testPropertyIsStatic:
Body:
Return: $.modifyStaticPropertyOnInstance()
modifyStaticPropertyOnInstance:
Usage: Static
Body:
- $obj1: new(TestStatics)
- $obj2: new(TestStatics)
- $obj1.modifyStaticPropertyUsingClassName()
- Return: $obj2.staticProperty
testStaticPropertisNotLoaded:
Body:
Return: $.staticProperty2
testTypeIsSingleton:
Body:
- $t11: :TestStatics
- $t12: :TestStatics
- $t21: ns:TestStatics
- $t22: ns:TestStatics
- $t31: type('test.TestStatics')
- $t32: type('test.TestStatics')
- Return: $t11 = $t12 and $t21 = $t22 and $t31 = $t32
testStaticPropertyInheritance:
Body:
Return: $.baseStaticProperty +
:TestStaticsBase.baseStaticProperty +
:TestStatics.baseStaticProperty
testStaticPropertyOverride:
Body:
Return:
- $.conflictingStaticProperty
- :TestStatics.conflictingStaticProperty
- :TestStaticsBase.conflictingStaticProperty
- type('test.TestStatics').conflictingStaticProperty
- type('test.TestStaticsBase').conflictingStaticProperty
testTypeinfoOfType:
Body:
- $typeObj: type('test.TestStatics')
- $typeInfoOfType: typeinfo($typeObj)
- $obj: new('TestStatics')
- Return: typeinfo($obj) = $typeInfoOfType

View File

@ -0,0 +1,15 @@
Namespaces:
=: test
Name: TestStaticsBase
Properties:
baseStaticProperty:
Contract: $.string()
Default: baseStaticProperty
Usage: Static
conflictingStaticProperty:
Contract: $.string()
Default: 'conflictingStaticProperty-base'
Usage: Static

View File

@ -0,0 +1,106 @@
# 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 murano.dsl import dsl_types
from murano.dsl import exceptions
from murano.tests.unit.dsl.foundation import object_model as om
from murano.tests.unit.dsl.foundation import test_case
class TestStatics(test_case.DslTestCase):
def setUp(self):
super(TestStatics, self).setUp()
self._runner = self.new_runner(
om.Object('test.TestStatics', staticProperty2='INVALID'))
def test_call_static_method_on_object(self):
self.assertEqual(123, self._runner.testCallStaticMethodOnObject())
def test_call_static_method_on_class_name(self):
self.assertEqual(123, self._runner.testCallStaticMethodOnClassName())
def test_call_static_method_on_class_name_with_ns(self):
self.assertEqual(
123, self._runner.testCallStaticMethodOnClassNameWithNs())
def test_call_static_method_from_another_method(self):
self.assertEqual(
123 * 5, self._runner.testCallStaticMethodFromAnotherMethod())
def test_static_this(self):
self.assertIsInstance(
self._runner.testStaticThis(), dsl_types.MuranoTypeReference)
def test_no_access_to_instance_properties(self):
self.assertRaises(
exceptions.NoPropertyFound,
self._runner.testNoAccessToInstanceProperties)
def test_access_static_property_from_instance_method(self):
self.assertEqual(
'xxx', self._runner.testAccessStaticPropertyFromInstanceMethod())
def test_access_static_property_from_static_method(self):
self.assertEqual(
'xxx', self._runner.testAccessStaticPropertyFromStaticMethod())
def test_modify_static_property_using_dollar(self):
self.assertEqual(
'qq', self._runner.testModifyStaticPropertyUsingDollar())
def test_modify_static_property_using_this(self):
self.assertEqual(
'qq', self._runner.testModifyStaticPropertyUsingThis())
def test_modify_static_property_using_class_name(self):
self.assertEqual(
'qq', self._runner.testModifyStaticPropertyUsingClassName())
def test_modify_static_property_using_ns_class_name(self):
self.assertEqual(
'qq', self._runner.testModifyStaticPropertyUsingNsClassName())
def test_modify_static_property_using_type_func(self):
self.assertEqual(
'qq', self._runner.testModifyStaticPropertyUsingTypeFunc())
def test_property_is_static(self):
self.assertEqual('qq', self._runner.testPropertyIsStatic())
def test_static_properties_excluded_from_object_model(self):
self.assertEqual(
'staticProperty',
self._runner.testStaticPropertisNotLoaded())
def test_type_is_singleton(self):
self.assertTrue(self._runner.testTypeIsSingleton())
def test_static_property_inheritance(self):
self.assertEqual(
'baseStaticProperty' * 3,
self._runner.testStaticPropertyInheritance())
def test_static_property_override(self):
self.assertEqual(
[
'conflictingStaticProperty-child',
'conflictingStaticProperty-child',
'conflictingStaticProperty-base',
'conflictingStaticProperty-child',
'conflictingStaticProperty-base'
], self._runner.testStaticPropertyOverride())
def test_type_info_of_type(self):
self.assertTrue(self._runner.testTypeinfoOfType())

View File

@ -50,23 +50,24 @@ class TestNamespaceResolving(base.MuranoTestCase):
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'}) resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
name = 'sys:' name = 'sys:'
self.assertRaises(NameError, resolver.resolve_name, name) self.assertRaises(ValueError, resolver.resolve_name, name)
def test_fails_w_excessive_prefix(self): def test_fails_w_excessive_prefix(self):
ns = {'sys': 'com.example.murano.system'} ns = {'sys': 'com.example.murano.system'}
resolver = ns_resolver.NamespaceResolver(ns) resolver = ns_resolver.NamespaceResolver(ns)
invalid_name = 'sys:excessive_ns:muranoResource' invalid_name = 'sys:excessive_ns:muranoResource'
self.assertRaises(NameError, resolver.resolve_name, invalid_name) self.assertRaises(ValueError, resolver.resolve_name, invalid_name)
def test_cuts_empty_prefix(self): def test_empty_prefix_is_default(self):
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'}) resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
# name without prefix delimiter # name without prefix delimiter
name = 'some.arbitrary.name' name = 'some.arbitrary.name'
resolved_name = resolver.resolve_name(':' + name) resolved_name = resolver.resolve_name(':' + name)
self.assertEqual(name, resolved_name) self.assertEqual(
'com.example.murano.some.arbitrary.name', resolved_name)
def test_resolves_specified_ns_prefix(self): def test_resolves_specified_ns_prefix(self):
ns = {'sys': 'com.example.murano.system'} ns = {'sys': 'com.example.murano.system'}
@ -85,20 +86,6 @@ class TestNamespaceResolving(base.MuranoTestCase):
self.assertEqual(full_name, resolved_name) self.assertEqual(full_name, resolved_name)
def test_resolves_explicit_base(self):
resolver = ns_resolver.NamespaceResolver({'=': 'com.example.murano'})
resolved_name = resolver.resolve_name('Resource', relative='com.base')
self.assertEqual('com.base.Resource', resolved_name)
def test_resolves_explicit_base_w_empty_namespaces(self):
resolver = ns_resolver.NamespaceResolver({})
resolved_name = resolver.resolve_name('File', 'com.base')
self.assertEqual('com.base.File', resolved_name)
def test_resolves_w_empty_namespaces(self): def test_resolves_w_empty_namespaces(self):
resolver = ns_resolver.NamespaceResolver({}) resolver = ns_resolver.NamespaceResolver({})

View File

@ -0,0 +1,9 @@
---
features:
- "Static methods and properties were introduced.
Both properties and methods can be marked as Usage: Static
Statics can be accessed using ns:Class.property / ns:Class.method(),
:Class.property / :Class.method() to access class from current namespace
or type('full.name').property / type('full.name').method() to use full
type name."
- io.murano.configuration.Linux methods are now static