deb-murano/murano/dsl/dsl.py
Stan Lagun 068831ccd8 Package versioning
With this change MuranoPackage becomes first-class DSL citizen.
Packages have version, runtime_version (that is specified
in Format attribute of the manifest file) and a list of classes.
Previously engine used to have package loader which had most
of "load" functionality and class loader that mostly acted as an
adapter from package loader to interface that DSL used to
get classes. Now class loader is gone and is replaced with
package loader at the DSL level. Package loader is responsible
for loading packages by either package or class name (as it was
before) plus semantic_version spec (for example ">=1.2,<2.0").
Package loader can now keep track of several versions of the same
package.

Also packages now have requirements with version specs.
All class names that are encountered in application code are
looked up within requirements only. As a consequence
packages that use other packages without referencing
them explicitly will become broken. An exception from this rule
is core library which is referenced automatically.

Partially implements: blueprint murano-versioning

Change-Id: I8789ba45b6210e71bf4977a766f82b66d2a2d270
2015-09-03 12:06:42 +00:00

330 lines
10 KiB
Python

# 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.
import inspect
import os.path
import types
from yaql.language import expressions as yaql_expressions
from yaql.language import utils
from yaql.language import yaqltypes
from murano.dsl import constants
from murano.dsl import dsl_types
from murano.dsl import helpers
NO_VALUE = utils.create_marker('NO_VALUE')
def name(dsl_name):
def wrapper(cls):
cls.__murano_name = dsl_name
return cls
return wrapper
class MuranoType(yaqltypes.PythonType):
def __init__(self, murano_class, nullable=False, version_spec=None):
self.murano_class = murano_class
self.version_spec = version_spec
super(MuranoType, self).__init__(
(dsl_types.MuranoObject, MuranoObjectInterface), nullable)
def check(self, value, context, *args, **kwargs):
if not super(MuranoType, self).check(
value, context, *args, **kwargs):
return False
if isinstance(value, MuranoObjectInterface):
value = value.object
if value is None or isinstance(value, yaql_expressions.Expression):
return True
murano_class = self.murano_class
if isinstance(murano_class, types.StringTypes):
murano_class_name = murano_class
else:
murano_class_name = murano_class.name
return helpers.is_instance_of(
value, murano_class_name,
self.version_spec or helpers.get_type(context))
def convert(self, value, sender, context, function_spec, engine,
*args, **kwargs):
result = super(MuranoType, self).convert(
value, sender, context, function_spec, engine, *args, **kwargs)
if isinstance(result, dsl_types.MuranoObject):
return MuranoObjectInterface(result, engine)
return result
class ThisParameterType(yaqltypes.HiddenParameterType, yaqltypes.SmartType):
def __init__(self):
super(ThisParameterType, self).__init__(False)
def convert(self, value, sender, context, function_spec, engine,
*args, **kwargs):
this = helpers.get_this(context)
executor = helpers.get_executor(context)
return MuranoObjectInterface(this, engine, executor)
class InterfacesParameterType(yaqltypes.HiddenParameterType,
yaqltypes.SmartType):
def __init__(self):
super(InterfacesParameterType, self).__init__(False)
def convert(self, value, sender, context, function_spec, engine,
*args, **kwargs):
this = helpers.get_this(context)
return Interfaces(engine, this)
class MuranoTypeName(yaqltypes.LazyParameterType, yaqltypes.PythonType):
def __init__(self, nullable=False, context=None):
self._context = context
super(MuranoTypeName, self).__init__(
(dsl_types.MuranoClassReference,) + types.StringTypes, nullable)
def convert(self, value, sender, context, function_spec, engine,
*args, **kwargs):
context = self._context or context
if isinstance(value, yaql_expressions.Expression):
value = value(utils.NO_VALUE, context, engine)
value = super(MuranoTypeName, self).convert(
value, sender, context, function_spec, engine)
if isinstance(value, types.StringTypes):
murano_type = helpers.get_type(context)
value = dsl_types.MuranoClassReference(
helpers.get_class(
murano_type.namespace_resolver.resolve_name(
value), context))
return value
class MuranoObjectInterface(dsl_types.MuranoObjectInterface):
class DataInterface(object):
def __init__(self, object_interface):
object.__setattr__(self, '__object_interface', object_interface)
def __getattr__(self, item):
oi = getattr(self, '__object_interface')
return oi[item]
def __setattr__(self, key, value):
oi = getattr(self, '__object_interface')
oi[key] = value
class CallInterface(object):
def __init__(self, mpl_object, executor):
self.__object = mpl_object
self.__executor = executor
def __getattr__(self, item):
executor = self.__executor or helpers.get_executor()
def func(*args, **kwargs):
self._insert_instruction()
return self.__object.type.invoke(
item, executor, self.__object, args, kwargs,
helpers.get_context())
return func
@staticmethod
def _insert_instruction():
context = helpers.get_context()
if context:
frame = inspect.stack()[2]
location = dsl_types.ExpressionFilePosition(
os.path.abspath(frame[1]), frame[2],
-1, frame[2], -1)
context[constants.CTX_CURRENT_INSTRUCTION] = NativeInstruction(
frame[4][0].strip(), location)
def __init__(self, mpl_object, engine, executor=None):
self.__object = mpl_object
self.__executor = executor
self.__engine = engine
@property
def object(self):
return self.__object
@property
def id(self):
return self.__object.object_id
@property
def owner(self):
return self.__object.owner
@property
def type(self):
return self.__object.type
@property
def package(self):
return self.type.package
def data(self):
return MuranoObjectInterface.DataInterface(self)
@property
def extension(self):
return self.__object.extension
def cast(self, murano_class, version_spec=None):
return MuranoObjectInterface(
helpers.cast(
self.__object, murano_class,
version_spec or helpers.get_type()),
self.__engine, self.__executor)
def is_instance_of(self, murano_class, version_spec=None):
return helpers.is_instance_of(
self.__object, murano_class,
version_spec or helpers.get_type())
def ancestors(self):
return self.type.ancestors()
def __getitem__(self, item):
context = helpers.get_context()
return to_mutable(
self.__object.get_property(item, context), self.__engine)
def __setitem__(self, key, value):
context = helpers.get_context()
value = helpers.evaluate(value, context)
self.__object.set_property(key, value, context)
def __call__(self):
return MuranoObjectInterface.CallInterface(
self.object, self.__executor)
def __repr__(self):
return '<{0}>'.format(repr(self.object))
class YaqlInterface(object):
def __init__(self, engine, sender=utils.NO_VALUE):
self.__engine = engine
self.__sender = sender
@property
def context(self):
return self.__context
@property
def engine(self):
return self.__engine
@property
def sender(self):
return self.__sender
def on(self, sender):
return YaqlInterface(self.engine, sender)
def __getattr__(self, item):
def stub(*args, **kwargs):
context = helpers.get_context()
args = tuple(helpers.evaluate(arg, context) for arg in args)
kwargs = dict((key, helpers.evaluate(value, context))
for key, value in kwargs.iteritems())
return to_mutable(
context(item, self.engine, self.sender)(*args, **kwargs),
self.engine)
return stub
def __call__(self, __expression, *args, **kwargs):
context = helpers.get_context().create_child_context()
for i, param in enumerate(args):
context['$' + str(i + 1)] = helpers.evaluate(param, context)
for arg_name, arg_value in kwargs.iteritems():
context['$' + arg_name] = helpers.evaluate(arg_value, context)
parsed = self.engine(__expression)
res = parsed.evaluate(context=context)
return to_mutable(res, self.engine)
def __getitem__(self, item):
return helpers.get_context()[item]
def __setitem__(self, key, value):
helpers.get_context()[key] = value
class Interfaces(object):
def __init__(self, engine, mpl_object):
self.__engine = engine
self.__object = mpl_object
def yaql(self, sender=utils.NO_VALUE):
return YaqlInterface(self.__engine, sender)
def this(self):
return self.methods(self.__object)
def methods(self, mpl_object):
if mpl_object is None:
return None
return MuranoObjectInterface(mpl_object, self.__engine)
@property
def environment(self):
return helpers.get_environment()
@property
def caller(self):
caller_context = helpers.get_caller_context()
if caller_context is None:
return None
caller = helpers.get_this(caller_context)
if caller is None:
return None
return MuranoObjectInterface(caller, self.__engine)
@property
def attributes(self):
executor = helpers.get_executor()
return executor.attribute_store
@property
def class_config(self):
return self.__object.type.package.get_class_config(
self.__object.type.name)
@property
def package_loader(self):
return helpers.get_package_loader()
class NativeInstruction(object):
def __init__(self, instruction, location):
self.instruction = instruction
self.source_file_position = location
def __str__(self):
return self.instruction
def to_mutable(obj, yaql_engine):
def converter(value, limit_func, engine, rec):
if isinstance(value, dsl_types.MuranoObject):
return MuranoObjectInterface(value, engine)
else:
return utils.convert_output_data(value, limit_func, engine, rec)
limiter = lambda it: utils.limit_iterable(it, constants.ITERATORS_LIMIT)
return converter(obj, limiter, yaql_engine, converter)