
1.As mentioned in [1], we should avoid using six.iteritems to achieve iterators. We can use dict.items instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2.In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html Change-Id: I45fa65427318e1c35bb521de46e81ea12ca7b770
235 lines
8.8 KiB
Python
235 lines
8.8 KiB
Python
# Copyright (c) 2014 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 warnings
|
|
import weakref
|
|
|
|
import debtcollector
|
|
import semantic_version
|
|
import six
|
|
from yaql.language import specs
|
|
from yaql.language import utils
|
|
|
|
from murano.dsl import constants
|
|
from murano.dsl import dsl_types
|
|
from murano.dsl import exceptions
|
|
from murano.dsl import helpers
|
|
from murano.dsl import meta as dslmeta
|
|
from murano.dsl import murano_object
|
|
from murano.dsl import murano_type
|
|
from murano.dsl import namespace_resolver
|
|
from murano.dsl import principal_objects
|
|
from murano.dsl import yaql_integration
|
|
|
|
|
|
class MuranoPackage(dsl_types.MuranoPackage, dslmeta.MetaProvider):
|
|
def __init__(self, package_loader, name, version=None,
|
|
runtime_version=None, requirements=None, meta=None):
|
|
super(MuranoPackage, self).__init__()
|
|
self._package_loader = weakref.proxy(package_loader)
|
|
self._name = name
|
|
self._version = helpers.parse_version(version)
|
|
self._runtime_version = helpers.parse_version(runtime_version)
|
|
self._requirements = {
|
|
name: semantic_version.Spec('==' + str(self._version.major))
|
|
}
|
|
if name != constants.CORE_LIBRARY:
|
|
self._requirements[constants.CORE_LIBRARY] = \
|
|
semantic_version.Spec('==0')
|
|
self._classes = {}
|
|
self._imported_types = {object, murano_object.MuranoObject}
|
|
for key, value in (requirements or {}).items():
|
|
self._requirements[key] = helpers.parse_version_spec(value)
|
|
|
|
self._load_queue = {}
|
|
self._native_load_queue = {}
|
|
if self.name == constants.CORE_LIBRARY:
|
|
principal_objects.register(self)
|
|
self._package_class = self._create_package_class()
|
|
self._meta = lambda: dslmeta.MetaData(
|
|
meta, dsl_types.MetaTargets.Package, self._package_class)
|
|
|
|
@property
|
|
def package_loader(self):
|
|
return self._package_loader
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
@property
|
|
def version(self):
|
|
return self._version
|
|
|
|
@property
|
|
def runtime_version(self):
|
|
return self._runtime_version
|
|
|
|
@property
|
|
def requirements(self):
|
|
return self._requirements
|
|
|
|
@property
|
|
def classes(self):
|
|
return set(self._classes.keys()).union(
|
|
self._load_queue.keys()).union(self._native_load_queue.keys())
|
|
|
|
def get_resource(self, name):
|
|
raise NotImplementedError('resource API is not implemented')
|
|
|
|
# noinspection PyMethodMayBeStatic
|
|
def get_class_config(self, name):
|
|
return {}
|
|
|
|
def _register_mpl_classes(self, data, name=None):
|
|
type_obj = self._classes.get(name)
|
|
if type_obj is not None:
|
|
return type_obj
|
|
if callable(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.copy())
|
|
if len(cls_data) == 1:
|
|
continue
|
|
cls_name = cls_data.get('Name')
|
|
if not cls_name:
|
|
if unnamed_class:
|
|
raise exceptions.AmbiguousClassName(name)
|
|
unnamed_class = cls_data
|
|
else:
|
|
ns_resolver = namespace_resolver.NamespaceResolver(last_ns)
|
|
cls_name = ns_resolver.resolve_name(cls_name)
|
|
if cls_name == name:
|
|
type_obj = murano_type.create(
|
|
cls_data, self, cls_name, ns_resolver)
|
|
self._classes[name] = type_obj
|
|
else:
|
|
self._load_queue.setdefault(cls_name, cls_data)
|
|
if type_obj is None and unnamed_class:
|
|
unnamed_class['Name'] = name
|
|
return self._register_mpl_classes(unnamed_class, name)
|
|
return type_obj
|
|
|
|
def _register_native_class(self, cls, name):
|
|
if cls in self._imported_types:
|
|
return self._classes[name]
|
|
|
|
try:
|
|
m_class = self.find_class(name, False)
|
|
except exceptions.NoClassFound:
|
|
m_class = self._register_mpl_classes({'Name': name}, name)
|
|
|
|
m_class.extension_class = cls
|
|
|
|
for method_name in dir(cls):
|
|
if method_name.startswith('_'):
|
|
continue
|
|
method = getattr(cls, method_name)
|
|
if not any((
|
|
helpers.inspect_is_method(cls, method_name),
|
|
helpers.inspect_is_static(cls, method_name),
|
|
helpers.inspect_is_classmethod(cls, method_name))):
|
|
continue
|
|
method_name_alias = (getattr(
|
|
method, '__murano_name', None) or
|
|
specs.convert_function_name(
|
|
method_name, yaql_integration.CONVENTION))
|
|
m_class.add_method(method_name_alias, method, method_name)
|
|
self._imported_types.add(cls)
|
|
return m_class
|
|
|
|
def register_class(self, cls, name=None):
|
|
if inspect.isclass(cls):
|
|
name = name or getattr(cls, '__murano_name', None) or cls.__name__
|
|
if name in self._classes:
|
|
self._register_native_class(cls, name)
|
|
else:
|
|
self._native_load_queue.setdefault(name, cls)
|
|
elif isinstance(cls, dsl_types.MuranoType):
|
|
self._classes[cls.name] = cls
|
|
elif name not in self._classes:
|
|
self._load_queue[name] = cls
|
|
|
|
def find_class(self, name, search_requirements=True):
|
|
payload = self._native_load_queue.pop(name, None)
|
|
if payload is not None:
|
|
return self._register_native_class(payload, name)
|
|
|
|
payload = self._load_queue.pop(name, None)
|
|
if payload is not None:
|
|
result = self._register_mpl_classes(payload, name)
|
|
if result:
|
|
return result
|
|
|
|
result = self._classes.get(name)
|
|
if result:
|
|
return result
|
|
if search_requirements:
|
|
pkgs_for_search = []
|
|
for package_name, version_spec in self._requirements.items():
|
|
if package_name == self.name:
|
|
continue
|
|
referenced_package = self._package_loader.load_package(
|
|
package_name, version_spec)
|
|
try:
|
|
return referenced_package.find_class(name, False)
|
|
except exceptions.NoClassFound:
|
|
if name.startswith('io.murano.extensions'):
|
|
try:
|
|
short_name = name.replace(
|
|
'io.murano.extensions.', '', 1)
|
|
result = referenced_package.find_class(
|
|
short_name, False)
|
|
warnings.simplefilter("once")
|
|
msg = ("Plugin %(name)s was not found, but a "
|
|
"%(shorter_name)s was found instead and "
|
|
"will be used. This could be caused by "
|
|
"recent change in plugin naming scheme. If "
|
|
"you are developing applications targeting "
|
|
"this plugin consider changing its name" %
|
|
{'name': name, 'shorter_name': short_name})
|
|
debtcollector.deprecate(msg)
|
|
return result
|
|
except exceptions.NoClassFound:
|
|
pass
|
|
pkgs_for_search.append(referenced_package)
|
|
continue
|
|
raise exceptions.NoClassFound(
|
|
name, packages=pkgs_for_search + [self])
|
|
|
|
raise exceptions.NoClassFound(name, packages=[self])
|
|
|
|
@property
|
|
def context(self):
|
|
return None
|
|
|
|
def _create_package_class(self):
|
|
ns_resolver = namespace_resolver.NamespaceResolver(None)
|
|
return murano_type.MuranoClass(
|
|
ns_resolver, self.name, self, utils.NO_VALUE)
|
|
|
|
def get_meta(self, context):
|
|
if six.callable(self._meta):
|
|
executor = helpers.get_executor()
|
|
context = executor.create_package_context(self)
|
|
self._meta = self._meta().get_meta(context)
|
|
return self._meta
|
|
|
|
def __repr__(self):
|
|
return 'MuranoPackage({name})'.format(name=self.name)
|