744 lines
28 KiB
Python
744 lines
28 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 copy
|
|
import re
|
|
|
|
import semantic_version
|
|
import six
|
|
|
|
from glance.common import exception as exc
|
|
from glance.i18n import _
|
|
|
|
|
|
class AttributeDefinition(object):
|
|
"""A base class for the attribute definitions which may be added to
|
|
declaratively defined artifact types
|
|
"""
|
|
|
|
ALLOWED_TYPES = (object,)
|
|
|
|
def __init__(self,
|
|
display_name=None,
|
|
description=None,
|
|
readonly=False,
|
|
mutable=True,
|
|
required=False,
|
|
default=None):
|
|
"""Initializes attribute definition
|
|
|
|
:param display_name: Display name of the attribute
|
|
:param description: Description of the attribute
|
|
:param readonly: Flag indicating if the value of attribute may not be
|
|
changed once an artifact is created
|
|
:param mutable: Flag indicating if the value of attribute may not be
|
|
changed once an artifact is published
|
|
:param required: Flag indicating if the value of attribute is required
|
|
:param default: default value of the attribute
|
|
"""
|
|
self.name = None
|
|
self.display_name = display_name
|
|
self.description = description
|
|
self.readonly = readonly
|
|
self.required = required
|
|
self.mutable = mutable
|
|
self.default = default
|
|
self._add_validator('type',
|
|
lambda v: isinstance(v, self.ALLOWED_TYPES),
|
|
_("Not a valid value type"))
|
|
self._validate_default()
|
|
|
|
def _set_name(self, value):
|
|
self.name = value
|
|
if self.display_name is None:
|
|
self.display_name = value
|
|
|
|
def _add_validator(self, name, func, message):
|
|
if not hasattr(self, '_validators'):
|
|
self._validators = []
|
|
self._validators_index = {}
|
|
pair = (func, message)
|
|
self._validators.append(pair)
|
|
self._validators_index[name] = pair
|
|
|
|
def _get_validator(self, name):
|
|
return self._validators_index.get(name)
|
|
|
|
def _remove_validator(self, name):
|
|
pair = self._validators_index.pop(name, None)
|
|
if pair is not None:
|
|
self._validators.remove(pair)
|
|
|
|
def _check_definition(self):
|
|
self._validate_default()
|
|
|
|
def _validate_default(self):
|
|
if self.default:
|
|
try:
|
|
self.validate(self.default, 'default')
|
|
except exc.InvalidArtifactPropertyValue:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Default value is invalid"))
|
|
|
|
def get_value(self, obj):
|
|
return getattr(obj, self.name)
|
|
|
|
def set_value(self, obj, value):
|
|
return setattr(obj, self.name, value)
|
|
|
|
def validate(self, value, name=None):
|
|
if value is None:
|
|
if self.required:
|
|
raise exc.InvalidArtifactPropertyValue(
|
|
name=name or self.name,
|
|
val=value,
|
|
msg=_('Value is required'))
|
|
else:
|
|
return
|
|
|
|
first_error = next((msg for v_func, msg in self._validators
|
|
if not v_func(value)), None)
|
|
if first_error:
|
|
raise exc.InvalidArtifactPropertyValue(name=name or self.name,
|
|
val=value,
|
|
msg=first_error)
|
|
|
|
|
|
class ListAttributeDefinition(AttributeDefinition):
|
|
"""A base class for Attribute definitions having List-semantics
|
|
|
|
Is inherited by Array, ArtifactReferenceList and BinaryObjectList
|
|
"""
|
|
ALLOWED_TYPES = (list,)
|
|
ALLOWED_ITEM_TYPES = (AttributeDefinition, )
|
|
|
|
def _check_item_type(self, item):
|
|
if not isinstance(item, self.ALLOWED_ITEM_TYPES):
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_('Invalid item type specification'))
|
|
if item.default is not None:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_('List definitions may hot have defaults'))
|
|
|
|
def __init__(self, item_type, min_size=0, max_size=None, unique=False,
|
|
**kwargs):
|
|
|
|
super(ListAttributeDefinition, self).__init__(**kwargs)
|
|
if isinstance(item_type, list):
|
|
for it in item_type:
|
|
self._check_item_type(it)
|
|
|
|
# we need to copy the item_type collection
|
|
self.item_type = item_type[:]
|
|
|
|
if min_size != 0:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Cannot specify 'min_size' explicitly")
|
|
)
|
|
|
|
if max_size is not None:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Cannot specify 'max_size' explicitly")
|
|
)
|
|
|
|
# setting max_size and min_size to the length of item_type,
|
|
# as tuple-semantic assumes that the number of elements is set
|
|
# by the type spec
|
|
min_size = max_size = len(item_type)
|
|
else:
|
|
self._check_item_type(item_type)
|
|
self.item_type = item_type
|
|
|
|
if min_size:
|
|
self.min_size(min_size)
|
|
|
|
if max_size:
|
|
self.max_size(max_size)
|
|
|
|
if unique:
|
|
self.unique()
|
|
|
|
def min_size(self, value):
|
|
self._min_size = value
|
|
if value is not None:
|
|
self._add_validator('min_size',
|
|
lambda v: len(v) >= self._min_size,
|
|
_('List size is less than minimum'))
|
|
else:
|
|
self._remove_validator('min_size')
|
|
|
|
def max_size(self, value):
|
|
self._max_size = value
|
|
if value is not None:
|
|
self._add_validator('max_size',
|
|
lambda v: len(v) <= self._max_size,
|
|
_('List size is greater than maximum'))
|
|
else:
|
|
self._remove_validator('max_size')
|
|
|
|
def unique(self, value=True):
|
|
self._unique = value
|
|
if value:
|
|
def _unique(items):
|
|
seen = set()
|
|
for item in items:
|
|
if item in seen:
|
|
return False
|
|
seen.add(item)
|
|
return True
|
|
self._add_validator('unique',
|
|
_unique, _('Items have to be unique'))
|
|
else:
|
|
self._remove_validator('unique')
|
|
|
|
def _set_name(self, value):
|
|
super(ListAttributeDefinition, self)._set_name(value)
|
|
if isinstance(self.item_type, list):
|
|
for i, item in enumerate(self.item_type):
|
|
item._set_name("%s[%i]" % (value, i))
|
|
else:
|
|
self.item_type._set_name("%s[*]" % value)
|
|
|
|
def validate(self, value, name=None):
|
|
super(ListAttributeDefinition, self).validate(value, name)
|
|
if value is not None:
|
|
for i, item in enumerate(value):
|
|
self._validate_item_at(item, i)
|
|
|
|
def get_item_definition_at_index(self, index):
|
|
if isinstance(self.item_type, list):
|
|
if index < len(self.item_type):
|
|
return self.item_type[index]
|
|
else:
|
|
return None
|
|
return self.item_type
|
|
|
|
def _validate_item_at(self, item, index):
|
|
item_type = self.get_item_definition_at_index(index)
|
|
# set name if none has been given to the list element at given index
|
|
if (isinstance(self.item_type, list) and item_type and
|
|
not item_type.name):
|
|
item_type.name = "%s[%i]" % (self.name, index)
|
|
if item_type:
|
|
item_type.validate(item)
|
|
|
|
|
|
class DictAttributeDefinition(AttributeDefinition):
|
|
"""A base class for Attribute definitions having Map-semantics
|
|
|
|
Is inherited by Dict
|
|
"""
|
|
ALLOWED_TYPES = (dict,)
|
|
ALLOWED_PROPERTY_TYPES = (AttributeDefinition,)
|
|
|
|
def _check_prop(self, key, item):
|
|
if (not isinstance(item, self.ALLOWED_PROPERTY_TYPES) or
|
|
(key is not None and not isinstance(key, six.string_types))):
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_('Invalid dict property type specification'))
|
|
|
|
@staticmethod
|
|
def _validate_key(key):
|
|
if not isinstance(key, six.string_types):
|
|
raise exc.InvalidArtifactPropertyValue(
|
|
_('Invalid dict property type'))
|
|
|
|
def __init__(self, properties, min_properties=0, max_properties=0,
|
|
**kwargs):
|
|
super(DictAttributeDefinition, self).__init__(**kwargs)
|
|
if isinstance(properties, dict):
|
|
for key, value in six.iteritems(properties):
|
|
self._check_prop(key, value)
|
|
# copy the properties dict
|
|
self.properties = properties.copy()
|
|
|
|
self._add_validator('keys',
|
|
lambda v: set(v.keys()) <= set(
|
|
self.properties.keys()),
|
|
_('Dictionary contains unexpected key(s)'))
|
|
else:
|
|
self._check_prop(None, properties)
|
|
self.properties = properties
|
|
|
|
if min_properties:
|
|
self.min_properties(min_properties)
|
|
|
|
if max_properties:
|
|
self.max_properties(max_properties)
|
|
|
|
def min_properties(self, value):
|
|
self._min_properties = value
|
|
if value is not None:
|
|
self._add_validator('min_properties',
|
|
lambda v: len(v) >= self._min_properties,
|
|
_('Dictionary size is less than '
|
|
'minimum'))
|
|
else:
|
|
self._remove_validator('min_properties')
|
|
|
|
def max_properties(self, value):
|
|
self._max_properties = value
|
|
if value is not None:
|
|
self._add_validator('max_properties',
|
|
lambda v: len(v) <= self._max_properties,
|
|
_('Dictionary size is '
|
|
'greater than maximum'))
|
|
else:
|
|
self._remove_validator('max_properties')
|
|
|
|
def _set_name(self, value):
|
|
super(DictAttributeDefinition, self)._set_name(value)
|
|
if isinstance(self.properties, dict):
|
|
for k, v in six.iteritems(self.properties):
|
|
v._set_name(value)
|
|
else:
|
|
self.properties._set_name(value)
|
|
|
|
def validate(self, value, name=None):
|
|
super(DictAttributeDefinition, self).validate(value, name)
|
|
if value is not None:
|
|
for k, v in six.iteritems(value):
|
|
self._validate_item_with_key(v, k)
|
|
|
|
def _validate_item_with_key(self, value, key):
|
|
self._validate_key(key)
|
|
if isinstance(self.properties, dict):
|
|
prop_def = self.properties.get(key)
|
|
if prop_def is not None:
|
|
name = "%s[%s]" % (prop_def.name, key)
|
|
prop_def.validate(value, name=name)
|
|
else:
|
|
name = "%s[%s]" % (self.properties.name, key)
|
|
self.properties.validate(value, name=name)
|
|
|
|
def get_prop_definition_at_key(self, key):
|
|
if isinstance(self.properties, dict):
|
|
return self.properties.get(key)
|
|
else:
|
|
return self.properties
|
|
|
|
|
|
class PropertyDefinition(AttributeDefinition):
|
|
"""A base class for Attributes defining generic or type-specific metadata
|
|
properties
|
|
"""
|
|
DB_TYPE = None
|
|
|
|
def __init__(self,
|
|
internal=False,
|
|
allowed_values=None,
|
|
validators=None,
|
|
**kwargs):
|
|
"""Defines a metadata property
|
|
|
|
:param internal: a flag indicating that the property is internal, i.e.
|
|
not returned to client
|
|
:param allowed_values: specifies a list of values allowed for the
|
|
property
|
|
:param validators: specifies a list of custom validators for the
|
|
property
|
|
"""
|
|
super(PropertyDefinition, self).__init__(**kwargs)
|
|
self.internal = internal
|
|
self._allowed_values = None
|
|
|
|
if validators is not None:
|
|
try:
|
|
for i, (f, m) in enumerate(validators):
|
|
self._add_validator("custom_%i" % i, f, m)
|
|
except ValueError:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Custom validators list should contain tuples "
|
|
"'(function, message)'"))
|
|
|
|
if allowed_values is not None:
|
|
# copy the allowed_values, as this is going to create a
|
|
# closure, and we need to make sure that external modification of
|
|
# this list does not affect the created validator
|
|
self.allowed_values(allowed_values)
|
|
self._check_definition()
|
|
|
|
def _validate_allowed_values(self):
|
|
if self._allowed_values:
|
|
try:
|
|
for allowed_value in self._allowed_values:
|
|
self.validate(allowed_value, 'allowed_value')
|
|
except exc.InvalidArtifactPropertyValue:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Allowed values %s are invalid under given validators") %
|
|
self._allowed_values)
|
|
|
|
def allowed_values(self, values):
|
|
self._allowed_values = values[:]
|
|
if values is not None:
|
|
self._add_validator('allowed', lambda v: v in self._allowed_values,
|
|
_("Is not allowed value"))
|
|
else:
|
|
self._remove_validator('allowed')
|
|
self._check_definition()
|
|
|
|
def _check_definition(self):
|
|
self._validate_allowed_values()
|
|
super(PropertyDefinition, self)._check_definition()
|
|
|
|
|
|
class RelationDefinition(AttributeDefinition):
|
|
"""A base class for Attributes defining cross-artifact relations"""
|
|
def __init__(self, internal=False, **kwargs):
|
|
self.internal = internal
|
|
kwargs.setdefault('mutable', False)
|
|
# if mutable=True has been passed -> raise an exception
|
|
if kwargs['mutable'] is True:
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Dependency relations cannot be mutable"))
|
|
super(RelationDefinition, self).__init__(**kwargs)
|
|
|
|
|
|
class BlobDefinition(AttributeDefinition):
|
|
"""A base class for Attributes defining binary objects"""
|
|
pass
|
|
|
|
|
|
class ArtifactTypeMetaclass(type):
|
|
"""A metaclass to build Artifact Types. Not intended to be used directly
|
|
|
|
Use `get_declarative_base` to get the base class instead
|
|
"""
|
|
def __init__(cls, class_name, bases, attributes):
|
|
if '_declarative_artifact_type' not in cls.__dict__:
|
|
_build_declarative_meta(cls)
|
|
super(ArtifactTypeMetaclass, cls).__init__(class_name, bases,
|
|
attributes)
|
|
|
|
|
|
class ArtifactPropertyDescriptor(object):
|
|
"""A descriptor object for working with artifact attributes"""
|
|
|
|
def __init__(self, prop, collection_wrapper_class=None):
|
|
self.prop = prop
|
|
self.collection_wrapper_class = collection_wrapper_class
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
# accessed via owner class
|
|
return self.prop
|
|
else:
|
|
v = getattr(instance, '_' + self.prop.name, None)
|
|
if v is None and self.prop.default is not None:
|
|
v = copy.copy(self.prop.default)
|
|
self.__set__(instance, v, ignore_mutability=True)
|
|
return self.__get__(instance, owner)
|
|
else:
|
|
if v is not None and self.collection_wrapper_class:
|
|
if self.prop.readonly:
|
|
readonly = True
|
|
elif (not self.prop.mutable and
|
|
hasattr(instance, '__is_mutable__') and
|
|
not hasattr(instance,
|
|
'__suspend_mutability_checks__')):
|
|
|
|
readonly = not instance.__is_mutable__()
|
|
else:
|
|
readonly = False
|
|
if readonly:
|
|
v = v.__make_immutable__()
|
|
return v
|
|
|
|
def __set__(self, instance, value, ignore_mutability=False):
|
|
if instance:
|
|
if self.prop.readonly:
|
|
if hasattr(instance, '_' + self.prop.name):
|
|
raise exc.InvalidArtifactPropertyValue(
|
|
_('Attempt to set readonly property'))
|
|
if not self.prop.mutable:
|
|
if (hasattr(instance, '__is_mutable__') and
|
|
not hasattr(instance,
|
|
'__suspend_mutability_checks__')):
|
|
mutable = instance.__is_mutable__() or ignore_mutability
|
|
if not mutable:
|
|
raise exc.InvalidArtifactPropertyValue(
|
|
_('Attempt to set value of immutable property'))
|
|
if value is not None and self.collection_wrapper_class:
|
|
value = self.collection_wrapper_class(value)
|
|
value.property = self.prop
|
|
self.prop.validate(value)
|
|
setattr(instance, '_' + self.prop.name, value)
|
|
|
|
|
|
class ArtifactAttributes(object):
|
|
"""A container class storing description of Artifact Type attributes"""
|
|
def __init__(self):
|
|
self.properties = {}
|
|
self.dependencies = {}
|
|
self.blobs = {}
|
|
self.all = {}
|
|
|
|
@property
|
|
def default_dependency(self):
|
|
"""Returns the default dependency relation for an artifact type"""
|
|
if len(self.dependencies) == 1:
|
|
return self.dependencies.values()[0]
|
|
|
|
@property
|
|
def default_blob(self):
|
|
"""Returns the default blob object for an artifact type"""
|
|
if len(self.blobs) == 1:
|
|
return self.blobs.values()[0]
|
|
|
|
@property
|
|
def default_properties_dict(self):
|
|
"""Returns a default properties dict for an artifact type"""
|
|
dict_props = [v for v in self.properties.values() if
|
|
isinstance(v, DictAttributeDefinition)]
|
|
if len(dict_props) == 1:
|
|
return dict_props[0]
|
|
|
|
@property
|
|
def tags(self):
|
|
"""Returns tags property for an artifact type"""
|
|
return self.properties.get('tags')
|
|
|
|
def add(self, attribute):
|
|
self.all[attribute.name] = attribute
|
|
if isinstance(attribute, PropertyDefinition):
|
|
self.properties[attribute.name] = attribute
|
|
elif isinstance(attribute, BlobDefinition):
|
|
self.blobs[attribute.name] = attribute
|
|
elif isinstance(attribute, RelationDefinition):
|
|
self.dependencies[attribute.name] = attribute
|
|
|
|
|
|
class ArtifactTypeMetadata(object):
|
|
"""A container to store the meta-information about an artifact type"""
|
|
|
|
def __init__(self, type_name, type_display_name, type_version,
|
|
type_description, endpoint):
|
|
"""Initializes the Artifact Type metadata
|
|
|
|
:param type_name: name of the artifact type
|
|
:param type_display_name: display name of the artifact type
|
|
:param type_version: version of the artifact type
|
|
:param type_description: description of the artifact type
|
|
:param endpoint: REST API URI suffix to call the artifacts of this type
|
|
"""
|
|
|
|
self.attributes = ArtifactAttributes()
|
|
|
|
# These are going to be defined by third-party plugin
|
|
# developers, so we need to do some validations on these values and
|
|
# raise InvalidArtifactTypeDefinition if they are violated
|
|
self.type_name = type_name
|
|
self.type_display_name = type_display_name or type_name
|
|
self.type_version = type_version or '1.0'
|
|
self.type_description = type_description
|
|
self.endpoint = endpoint or type_name.lower()
|
|
|
|
self._validate_string(self.type_name, 'Type name', min_length=1,
|
|
max_length=255)
|
|
self._validate_string(self.type_display_name, 'Type display name',
|
|
max_length=255)
|
|
self._validate_string(self.type_description, 'Type description')
|
|
self._validate_string(self.endpoint, 'endpoint', min_length=1)
|
|
try:
|
|
semantic_version.Version(self.type_version, partial=True)
|
|
except ValueError:
|
|
raise exc.InvalidArtifactTypeDefinition(
|
|
message=_("Type version has to be a valid semver string"))
|
|
|
|
@staticmethod
|
|
def _validate_string(value, name, min_length=0, max_length=None,
|
|
pattern=None):
|
|
if value is None:
|
|
if min_length > 0:
|
|
raise exc.InvalidArtifactTypeDefinition(
|
|
message=_("%(attribute)s is required"), attribute=name)
|
|
else:
|
|
return
|
|
if not isinstance(value, six.string_types):
|
|
raise exc.InvalidArtifactTypeDefinition(
|
|
message=_("%(attribute)s have to be string"), attribute=name)
|
|
if max_length and len(value) > max_length:
|
|
raise exc.InvalidArtifactTypeDefinition(
|
|
message=_("%(attribute)s may not be longer than %(length)i"),
|
|
attribute=name, length=max_length)
|
|
if min_length and len(value) < min_length:
|
|
raise exc.InvalidArtifactTypeDefinition(
|
|
message=_("%(attribute)s may not be shorter than %(length)i"),
|
|
attribute=name, length=min_length)
|
|
if pattern and not re.match(pattern, value):
|
|
raise exc.InvalidArtifactTypeDefinition(
|
|
message=_("%(attribute)s should match pattern %(pattern)s"),
|
|
attribute=name, pattern=pattern.pattern)
|
|
|
|
|
|
def _build_declarative_meta(cls):
|
|
attrs = dict(cls.__dict__)
|
|
type_name = None
|
|
type_display_name = None
|
|
type_version = None
|
|
type_description = None
|
|
endpoint = None
|
|
|
|
for base in cls.__mro__:
|
|
for name, value in six.iteritems(vars(base)):
|
|
if name == '__type_name__':
|
|
if not type_name:
|
|
type_name = cls.__type_name__
|
|
elif name == '__type_version__':
|
|
if not type_version:
|
|
type_version = cls.__type_version__
|
|
elif name == '__type_description__':
|
|
if not type_description:
|
|
type_description = cls.__type_description__
|
|
elif name == '__endpoint__':
|
|
if not endpoint:
|
|
endpoint = cls.__endpoint__
|
|
elif name == '__type_display_name__':
|
|
if not type_display_name:
|
|
type_display_name = cls.__type_display_name__
|
|
elif base is not cls and name not in attrs:
|
|
if isinstance(value, AttributeDefinition):
|
|
attrs[name] = value
|
|
elif isinstance(value, ArtifactPropertyDescriptor):
|
|
attrs[name] = value.prop
|
|
|
|
meta = ArtifactTypeMetadata(type_name=type_name or cls.__name__,
|
|
type_display_name=type_display_name,
|
|
type_version=type_version,
|
|
type_description=type_description,
|
|
endpoint=endpoint)
|
|
setattr(cls, 'metadata', meta)
|
|
for k, v in attrs.items():
|
|
if k == 'metadata':
|
|
raise exc.InvalidArtifactTypePropertyDefinition(
|
|
_("Cannot declare artifact property with reserved name "
|
|
"'metadata'"))
|
|
if isinstance(v, AttributeDefinition):
|
|
v._set_name(k)
|
|
wrapper_class = None
|
|
if isinstance(v, ListAttributeDefinition):
|
|
wrapper_class = type("ValidatedList", (list,), {})
|
|
_add_validation_to_list(wrapper_class)
|
|
if isinstance(v, DictAttributeDefinition):
|
|
wrapper_class = type("ValidatedDict", (dict,), {})
|
|
_add_validation_to_dict(wrapper_class)
|
|
prop_descr = ArtifactPropertyDescriptor(v, wrapper_class)
|
|
setattr(cls, k, prop_descr)
|
|
meta.attributes.add(v)
|
|
|
|
|
|
def _validating_method(method, klass):
|
|
def wrapper(self, *args, **kwargs):
|
|
instance_copy = klass(self)
|
|
method(instance_copy, *args, **kwargs)
|
|
self.property.validate(instance_copy)
|
|
method(self, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
def _immutable_method(method):
|
|
def substitution(*args, **kwargs):
|
|
raise exc.InvalidArtifactPropertyValue(
|
|
_("Unable to modify collection in "
|
|
"immutable or readonly property"))
|
|
|
|
return substitution
|
|
|
|
|
|
def _add_immutable_wrappers(class_to_add, wrapped_methods):
|
|
for method_name in wrapped_methods:
|
|
method = getattr(class_to_add, method_name, None)
|
|
if method:
|
|
setattr(class_to_add, method_name, _immutable_method(method))
|
|
|
|
|
|
def _add_validation_wrappers(class_to_validate, base_class, validated_methods):
|
|
for method_name in validated_methods:
|
|
method = getattr(class_to_validate, method_name, None)
|
|
if method:
|
|
setattr(class_to_validate, method_name,
|
|
_validating_method(method, base_class))
|
|
readonly_class = type("Readonly" + class_to_validate.__name__,
|
|
(class_to_validate,), {})
|
|
_add_immutable_wrappers(readonly_class, validated_methods)
|
|
|
|
def __make_immutable__(self):
|
|
return readonly_class(self)
|
|
|
|
class_to_validate.__make_immutable__ = __make_immutable__
|
|
|
|
|
|
def _add_validation_to_list(list_based_class):
|
|
validated_methods = ['append', 'extend', 'insert', 'pop', 'remove',
|
|
'reverse', 'sort', '__setitem__', '__delitem__',
|
|
'__delslice__']
|
|
_add_validation_wrappers(list_based_class, list, validated_methods)
|
|
|
|
|
|
def _add_validation_to_dict(dict_based_class):
|
|
validated_methods = ['pop', 'popitem', 'setdefault', 'update',
|
|
'__delitem__', '__setitem__', 'clear']
|
|
_add_validation_wrappers(dict_based_class, dict, validated_methods)
|
|
|
|
|
|
def _kwarg_init_constructor(self, **kwargs):
|
|
self.__suspend_mutability_checks__ = True
|
|
try:
|
|
for k in kwargs:
|
|
if not hasattr(type(self), k):
|
|
raise exc.ArtifactInvalidProperty(prop=k)
|
|
setattr(self, k, kwargs[k])
|
|
self._validate_required(self.metadata.attributes.properties)
|
|
finally:
|
|
del self.__suspend_mutability_checks__
|
|
|
|
|
|
def _validate_required(self, attribute_dict):
|
|
for k, v in six.iteritems(attribute_dict):
|
|
if v.required and (not hasattr(self, k) or getattr(self, k) is None):
|
|
raise exc.InvalidArtifactPropertyValue(name=k, val=None,
|
|
msg=_('Value is required'))
|
|
|
|
|
|
def _update(self, values):
|
|
for k in values:
|
|
if hasattr(type(self), k):
|
|
setattr(self, k, values[k])
|
|
else:
|
|
raise exc.ArtifactInvalidProperty(prop=k)
|
|
|
|
|
|
def _pre_publish_validator(self, *args, **kwargs):
|
|
self._validate_required(self.metadata.attributes.blobs)
|
|
self._validate_required(self.metadata.attributes.dependencies)
|
|
|
|
|
|
_kwarg_init_constructor.__name__ = '__init__'
|
|
_pre_publish_validator.__name__ = '__pre_publish__'
|
|
_update.__name__ = 'update'
|
|
|
|
|
|
def get_declarative_base(name='base', base_class=object):
|
|
"""Returns a base class which should be inherited to construct Artifact
|
|
Type object using the declarative syntax of attribute definition
|
|
"""
|
|
bases = not isinstance(base_class, tuple) and (base_class,) or base_class
|
|
class_dict = {'__init__': _kwarg_init_constructor,
|
|
'_validate_required': _validate_required,
|
|
'__pre_publish__': _pre_publish_validator,
|
|
'_declarative_artifact_type': True,
|
|
'update': _update}
|
|
return ArtifactTypeMetaclass(name, bases, class_dict)
|