# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
# $Id: prop.py 9195 2009-10-12 20:41:19Z dmitri $
import sys
import copy
from decimal import Decimal
from datetime import datetime
from sets import ImmutableSet
from snaplogic.common.snap_exceptions import *
from snaplogic.snapi_base import resdef,keys
"""
User defined properties can be based on simple types like number, string, boolean or datetime. Such properties
are defined by the class SimpleProp. On the other hand, a property can be more complex and can be defined
using a list or a dictionary of SimpleProps. These lists or dictionaries could then be nested in other
lists and dictionaries to create arbitrarily complex properties.
Company :{
EmpList : [
Employee: {
Name:
Sex:
Age:
Address: {
street number:
street name:
City:
State:
zip:
}
}
]
}
This can be defined as property as follows:
addr_def = DictProp('Address Info', None, '', 4, 4)
addr_def['street number'] = SimpleProp('Street Number', number)
addr_def['street name'] = SimpleProp('Street Name', string)
addr_def['state'] = SimpleProp('State', string)
addr_def['city'] = SimpleProp('City', string)
addr_def['zip'] = SimpleProp('zip', number)
emp_def = DictProp('Employee Info', None, '', 4, 4)
emp_def['Name'] = SimpleProp('Name', string)
emp_def['Sex'] = SimpleProp('Sex', string)
emp_def['Age'] = SimpleProp('Age', number)
emp_def['Address'] = addr_def
emplist_def = ListProp('List of employees', emp_def, '', 0, 1000):
comp_def = DictProp('Company Info')
comp_def['EmpList'] = emplist_def
self.set_propertydef('Company', comp_def)
This definition code is not trivial, but the aim was to provide a reasonable interface for the most
common case (which is simple property) and provide a scalable mechanism for component developers
who need to do something more involved. The values of these user defined properties can set or got
by using the following methods
get_property_value(prop_name)
set_propertyval(prop_name, prop_val)
For the above structure, here are some sample uses:
v = self.get_property_value('Company')
v['EmplList'].append({'Name':'John Doe',
'Sex':'M',
'Age':39
'Address':{'...}})
self.set_propertyval('Company', v)
"""
# This dictionary maps the constraints to error messages that constraint validation should produce
# if constraint is violated.
LOV_ERROR_MESSAGES = {
ImmutableSet([keys.CONSTRAINT_LOV_INPUT_VIEW]) : "Expected an input view name: %s",
ImmutableSet([keys.CONSTRAINT_LOV_OUTPUT_VIEW]) : "Expected an output view name: %s",
ImmutableSet([keys.CONSTRAINT_LOV_INPUT_VIEW, keys.CONSTRAINT_LOV_OUTPUT_VIEW]) : "Expected a view name: %s",
ImmutableSet([keys.CONSTRAINT_LOV_INPUT_FIELD]) : "Expected an input field name: %s",
ImmutableSet([keys.CONSTRAINT_LOV_OUTPUT_FIELD]) : "Expected an output field name: %s",
ImmutableSet([keys.CONSTRAINT_LOV_INPUT_FIELD, keys.CONSTRAINT_LOV_OUTPUT_FIELD]) : "Expected a field name: %s",
}
class SimpleProp(object):
def __init__(self, label_str, simple_type, description_str = "", prop_constraint = None, required = False):
"""
Initializes a simple type property defintion.
Simple type properties are of the type number, string, datetime and boolean.
@param label_str: The label (UI friendly) string.
@type label_str: str
@param simple_type: The type of the property. Can be "number", "string", "datetime" and "boolean"
@type simple_type: str
@param description_str: Document string describing the property.
@type description_str: str
@param prop_constraint: Dictionary of constraints.
@type prop_constraint: dict
@param required: If True, then this property must exist. If this property is nested inside
another, then the property is required only if the enclosing property exists.
@type required: bool
"""
if label_str is None:
label_str = "Simple Property"
self.label_str = label_str
if simple_type not in resdef.type_and_constraints.keys():
raise SnapResDefError("%s is not a valid type" % simple_type)
self.simple_type = simple_type
self.description_str = description_str
resdef.validate_constraint(simple_type, prop_constraint)
self.prop_constraint = prop_constraint
self.required = required
def __str__(self):
s = []
s.append("Label: %s, Type: %s, Required: %s " % (self.label_str, self.simple_type, self.required))
s.append("Prop Constraint: %s, Description: %s\n" % (self.prop_constraint, self.description_str))
return "".join(s)
def __repr__(self):
return self.__str__()
def __eq__(self, s):
if ((not hasattr(s, "label_str") or self.label_str != s.label_str) or
(not hasattr(s, "simple_type") or self.simple_type != s.simple_type) or
(not hasattr(s, "prop_constraint") or self.prop_constraint != s.prop_constraint) or
(not hasattr(s, "description_str") or self.description_str != s.description_str) or
(not hasattr(s, "required") or self.required != s.required)):
return False
else:
return True
def __ne__(self, s):
return not self.__eq__(s)
def _to_resdef(self):
return resdef.get_resdef_format_simple_prop(self.label_str, self.simple_type,
self.description_str, self.prop_constraint,
self.required)
def validate(self, data, my_key = None, partial_data = False, all_dynamic_constraints = None):
"""
Validate the simple value using this simple prop class.
@param data: Data to be validated
@type data: simple values
@param my_key: If this value is an entry inside another dict or list, then the key for the
entry can be specified in this param. Used for creating more discriptive error messages.
@type my_key: str
@param partial_data: If set to True, then don't return error if all the values have not been provided.
For example, required property values may not be specified. So, just
@type partial_data: bool
@param all_dynamic_constraints: Dictionary mapping dynamic constraint name to constraint value.
For example, dynamic constraint "input field" may look like:
{keys.CONSTRAINT_LOV_INPUT_FIELD : ['city', 'state', 'zip'] }
Meaning that there are three input fields: city, state, and zip.
@type all_dynamic_constraints: dict
@return: If the value is not valid, then an error string is returned, else None is returned.
@rtype: str
"""
if data is None or (self.simple_type == "string" and data == ""):
if (not partial_data) and self.required:
return "Property '%s' requires a value" % self.label_str
else:
return None
# If there is an LOV constraint, expand
# dynamic constraints (e.g. "input field") into a list of values
# and pass that to validate_simple_prop_value.
prop_constraint = self.prop_constraint
custom_lov_error_message = None
if prop_constraint and 'lov' in prop_constraint:
# Make a copy of the constraint because we'll be changing it
# (expanding the list) -- so we don't overwrite the resdef.
prop_constraint = copy.copy(self.prop_constraint)
lov_constraint = []
# Used dynamic constraints lists all dynamic constraints used:
# for looking up the proper error message should the constraint not validate.
used_dynamic_constraints = []
for value in prop_constraint['lov']:
if value not in keys.CONSTRAINT_LOV_DYNAMIC_VALUES:
lov_constraint.append(value)
else:
used_dynamic_constraints.append(value)
lov_constraint.extend(all_dynamic_constraints[value])
custom_lov_error_message = LOV_ERROR_MESSAGES.get(ImmutableSet(used_dynamic_constraints))
if custom_lov_error_message:
custom_lov_error_message = custom_lov_error_message % (', '.join(lov_constraint))
prop_constraint['lov'] = lov_constraint
ret = resdef.validate_simple_prop_value(self.simple_type, data, prop_constraint, custom_lov_error_message)
if ret is not None:
return "Value for '%s' is invalid. %s" % (self.label_str, ret)
return None
DEFAULT_PROPERTY_TYPE = SimpleProp("Property", "string",)
class ListProp(object):
"""
A list property object provides a defintion of list container whose values are SimpleProp, ListProp or DictProp
definitions. All the normal list methods are supported, with the constraint that the specified max_size is not
exceeded.
"""
def __init__(self, label_str, default_entry_type = None, description_str = None,
min_size = 0, max_size = resdef.UNLIMITED_ENTRIES, required = False):
"""
Initializes a list type property definition.
@param label_str: The label (UI friendly) string.
@type label_str: str
@param default_entry_type: The type for entries in the list. Can be SimpleProp, ListProp or
DictProp definition. If no default value is specified, then string SimpleProp is assumed.
@type default_entry_type: L{SimpleProp} or L{ListProp} or L{DictProp}
@param description_str: Document string describing the property.
@type description_str: str
@param min_size: Minimum size of the list. Default value is 0.
@type min_size: int
@param max_size: Maximum size of the list. Default value is infinite.
@type max_size: int
@param required: If True, then this property must exist. If this property is nested inside
another, then the property is required only if the enclosing property exists.
@type required: bool
"""
if label_str is None:
label_str = "List Property"
self.label_str = label_str
if default_entry_type is None:
default_entry_type = DEFAULT_PROPERTY_TYPE
elif ((not isinstance(default_entry_type, SimpleProp)) and (not isinstance(default_entry_type, DictProp))
and (not isinstance(default_entry_type, ListProp))):
raise SnapResDefError("The default_entry_type needs to be a SimpleProp/ListProp/DictProp object")
self.default_entry_type = default_entry_type
self.description_str = description_str
resdef.validate_limits(min_size, max_size)
self.min_size = min_size
self.max_size = max_size
self.required = required
def __str__(self):
l = []
l.append("LIST TYPE:\nLabel: %s, Description: %s Min size: %s Max size: %s Required: %s\n"
% (self.label_str, self.description_str, self.min_size, self.max_size, self.required))
l.append("Default entry:\n%s\n" % str(self.default_entry_type))
return "".join(l)
def __eq__(self, s):
if ((not hasattr(s, "label_str") or self.label_str != s.label_str) or
(not hasattr(s, "description_str") or self.description_str != s.description_str) or
(not hasattr(s, "default_entry_type") or self.default_entry_type != s.default_entry_type) or
(not hasattr(s, "min_size") or self.min_size != s.min_size) or
(not hasattr(s, "max_size") or self.max_size != s.max_size) or
(not hasattr(s, "required") or self.required != s.required)):
return False
else:
return True
def __ne__(self, s):
return not self.__eq__(s)
def _is_int(self, i):
"""
Raise exception if param is not int.
@param i: Value to be tested
@type i: anything
@raise TypeError: If param is not an integer.
"""
invalid = False
try:
if int(i) != i:
invalid = True
except (TypeError, ValueError):
raise TypeError("list indices must be integers")
if invalid:
raise TypeError("list indices must be integers")
def _to_resdef(self):
r = resdef.get_resdef_format_list_prop(self.label_str, self.default_entry_type._to_resdef(),
self.description_str, self.min_size, self.max_size,
self.required)
r[keys.COMPOSITE_PROPERTY_STRUCT] = []
return r
def get_item_type(self, idx):
"""
This method returns the expected type of this list entry.
The method checks to see if the list definition has an explicitly specified type for the
item specified by the index number and returns it. If no explicit definition was found,
then default type for the list is returned.
@param idx: Index number
@type idx: int or long
@return: Expected type of the list entry.
@rtype: L{SimpleProp}, L{ListProp}, L{DictProp}
"""
# Do a quick check to make sure index is int
self._is_int(idx)
if idx > self.max_size:
raise IndexError("Values %s is outside the valid range for this ListProp (%s-%s)" %
(idx, self.min_size, self.max_size))
else:
return self.default_entry_type
def validate(self, data, my_key = None, partial_data = False, all_dynamic_constraints = None):
"""
Validate the list value using this list prop class.
@param data: Tha data to be validated
@type data: list
@param my_key: If this list is a nested entry inside another dict or list, then the key for the
entry can be specified in this param. Used for creating more discriptive error messages.
@type my_key: str
@param partial_data: If set to True, then don't return error if all the required entries have not
been provided or lower limit in the list has not been reached.
@type partial_data: bool
@param all_dynamic_constraints: See SimpleProp documentation for description of this parameter.
This parameter is not used here. It is only used with SimpleProp types.
The reason it's not used here is because it is only needed for validating
dynamic LOV constraints (see keys.CONSTRAINT_LOV_DYNAMIC_VALUES).
These constraints are only used with SimpleProp.
However even though this parameter isn't used it's provided here
to keep the signature of the validate method the same across
all property types (SimpleProp, ListProp, DictProp).
@return: If the list is not valid, then an error string is returned, else None is returned.
@rtype: str
"""
if data is None:
if (not partial_data) and self.required:
return "Property '%s' requires a value" % self.label_str
else:
return None
# Quick check to make sure its a list/tuple like object.
if not isinstance(data, list) and not isinstance(data, tuple):
return "%s: is supposed to be a list or tuple, received %s value - %s" \
% (self.label_str, type(data), data)
l = len(data)
if l > self.max_size:
return "%s: Length '%s' exceeds the maximum allowed '%s' for the list" % (self.label_str, l, self.max_size)
if (not partial_data) and l < self.min_size:
return "%s: Length '%s' is less than the minimum allowed '%s' for the list" % \
(self.label_str, l, self.min_size)
return None
class DictProp(dict):
"""
A dictionary property provides a dictionary whose values are SimpleProp, ListProp or DictProp.
The key is string or unicode. All the normal dictionary methods are supported, with the constraint
that the specified max_size is not exceeded.
"""
def __init__ (self, label_str, default_entry_type = None, description_str = None, min_size = 0,
max_size = resdef.UNLIMITED_ENTRIES, fixed_keys = False, required = False):
"""
Initializes a dict type property defintion.
@param label_str: The label (UI friendly) string.
@type label_str: str
@param default_entry_type: The default type for entries in the dict. Can be SimpleProp, ListProp or
DictProp definition. If no default value is specified, then string SimpleProp is used.
@type default_entry_type: L{SimpleProp} or L{ListProp} or L{DictProp}
@param description_str: Document string describing the property.
@type description_str: str
@param min_size: Minimum size of the dict. Default value is 0.
@type min_size: int
@param max_size: Maximum size of the dict. Default value is infinite.
@type max_size: int
@param fixed_keys: Set to True, if each entry in the dictionary is explicitly defined. The max/min
limits are ignored if this is set to True.
@type fixed_keys: bool
@param required: If True, then this property must exist. If this property is nested inside
another, then the property is required only if the enclosing property exists.
@type required: bool
"""
dict.__init__(self)
if label_str is None:
label_str = "Dict Property"
self.label_str = label_str
if default_entry_type is None:
default_entry_type = DEFAULT_PROPERTY_TYPE
elif ((not isinstance(default_entry_type, SimpleProp)) and (not isinstance(default_entry_type, DictProp))
and (not isinstance(default_entry_type, ListProp))):
raise SnapResDefError("The default_entry_type needs to be a SimpleProp/ListProp/DictProp object")
self.default_entry_type = default_entry_type
self.description_str = description_str
resdef.validate_limits(min_size, max_size)
self.min_size = min_size
self.max_size = max_size
self.fixed_keys = fixed_keys
self.required = required
def __str__(self):
l = []
l.append("DICT TYPE:\nLabel: %s, Description %s Min size %s Max size %s FixedKeys %s Required %s\n"
% (self.label_str, self.description_str, self.min_size, self.max_size, self.fixed_keys, self.required))
l.append("Default entry %s" % str(self.default_entry_type))
for s in self:
l.append("Entry: %s->%s" % (s, self[s]))
return "".join(l)
def __eq__(self, s):
if ((not hasattr(s, "label_str") or self.label_str != s.label_str) or
(not hasattr(s, "description_str") or self.description_str != s.description_str) or
(not hasattr(s, "default_entry_type") or self.default_entry_type != s.default_entry_type) or
(not hasattr(s, "min_size") or self.min_size != s.min_size) or
(not hasattr(s, "max_size") or self.max_size != s.max_size) or
(not hasattr(s, "required") or self.required != s.required) or
(not hasattr(s, "fixed_keys") or self.fixed_keys != s.fixed_keys) or
dict(self) != dict(s)):
return False
else:
return True
def __ne__(self, s):
return not self.__eq__(s)
def __setitem__(self, key, value):
"""
Intercept the l[key]=value operations.
@param key: Key for the entry
@type key: str
@param value: Value for the entry
@type value: Varies, can be SimpleProp or a more complex property like DictProp or ListProp.
"""
if ((value is not None) and (not isinstance(value, SimpleProp)) and (not isinstance(value, DictProp))
and (not isinstance(value, ListProp))):
raise SnapResDefError("The value needs to be a SimpleProp/ListProp/DictProp/None object")
if key not in self and len(self) >= self.max_size:
raise SnapResDefError("New value exceed upper limit (%s) for entries in the dictionary" % self.max_size)
dict.__setitem__(self, key, value)
def update(self, update_dict):
"""
Update method of a dictionary container.
@param update_dict: The dictionary being used for update
@type update_dict: L{DictProp}
"""
i = 0
for key, value in update_dict.items():
if key not in self:
i += 1
if ((value is not None) and (not isinstance(value, SimpleProp)) and (not isinstance(value, DictProp))
and (not isinstance(value, ListProp))):
raise SnapResDefError("The value needs to be a SimpleProp/ListProp/DictProp/None object")
if len(self) + i > self.max_size:
raise SnapResDefError("New values exceed upper limit (%s) for entries in the dictionary" % self.max_size)
dict.update(self, update_dict)
def setdefault(self, key, value = None):
"""
The setdefault functionality of a dictionary.
If None is specified for value, then the default will be set to the default
type definition provided to the dictionary at initialization time (not None).
@param key: Key for the setdefault call
@type key: str
@param value: The default value. If not specified or set to None, then the default_entry_type is
used.
@type value: None or L{SimpleProp}, L{DictProp} or L{ListProp}
@return: The value at the key of default entry.
@rtype: L{SimpleProp} or L{DictProp} or L{ListProp}
"""
if value is None:
value = self.default_entry_type
elif ((not isinstance(value, SimpleProp)) and (not isinstance(value, DictProp))
and (not isinstance(value, ListProp))):
raise SnapResDefError("The value needs to be a SimpleProp/ListProp/DictProp object")
if key not in self and len(self) == self.max_size:
raise SnapResDefError("New value exceeds upper limit (%s) for entries in the dictionary" % self.max_size)
return dict.setdefault(self, key, value)
def _to_resdef(self):
r = resdef.get_resdef_format_dict_prop(self.label_str, self.default_entry_type._to_resdef(),
self.description_str, self.min_size, self.max_size,
self.fixed_keys, self.required)
for k in self:
r[keys.COMPOSITE_PROPERTY_STRUCT][k] = self[k]._to_resdef()
return r
def get_item_type(self, key):
"""
This method returns the expected type of this dict entry.
The method checks to see if the dict definition has an explicitly specified type for the
item and returns it. If no explicit definition was found, then default type for the dict
is returned.
@param key: Key of the item
@type key: str
@return: Expected type of the dict entry.
@rtype: L{SimpleProp}, L{ListProp}, L{DictProp}
"""
if key in self:
return self[key]
else:
return self.default_entry_type
def validate(self, data, my_key = None, partial_data = False, all_dynamic_constraints = None):
"""
Validate the dictionary value using this dict prop class.
@param data: The data to be validated
@type data: dict
@param my_key: If this dict is a nested entry inside another dict or list, then the key for the
entry can be specified in this param. Used for creating more discriptive error messages.
@type my_key: str
@param partial_data: If set to True, then don't return error if all the required entries have not
been provided or lower limit in the dict has not been reached.
@type partial_data: bool
@param all_dynamic_constraints: See SimpleProp documentation for description of this parameter.
This parameter is not used here. It is only used with SimpleProp types.
The reason it's not used here is because it is only needed for validating
dynamic LOV constraints (see keys.CONSTRAINT_LOV_DYNAMIC_VALUES).
These constraints are only used with SimpleProp.
However even though this parameter isn't used it's provided here
to keep the signature of the validate method the same across
all property types (SimpleProp, ListProp, DictProp).
@return: If the dict is not valid, then an error string is returned, else None is returned.
@rtype: str
"""
if data is None:
if (not partial_data) and self.required:
return "Property '%s' requires a value" % self.label_str
else:
return None
# Quick check to make sure its a dict like object.
if not (hasattr(data, "keys") and callable(data.keys)):
return "%s: Should be a dictionary, received %s value - %s" % (self.label_str, type(data), data)
l = len(data)
if l > self.max_size:
return "%s: Length (%s) greater than the maximum allowed (%s) for the dictionary" % \
(self.label_str, l, self.max_size)
if (not partial_data) and l < self.min_size:
return "%s: Length (%s) less than the minimum allowed (%s) for the dictionary" % \
(self.label_str, l, self.min_size)
# Make sure all required entries are present in the dictionary
for k in self:
if self[k] is None:
# This entry has been left undefined.
continue
if (not partial_data) and self[k].required:
if k not in data:
return "%s: Missing required key %s" % (self.label_str, k)
"""
elif data[k] is None:
# The error message has been made more user friendly as per #1284. This message (unlike many
# others in this method) can occur frequently due to operator error and should have a message
# which is user friendly.
return "Property '%s' requires a value" % k
"""
if self.fixed_keys:
for k in data:
if k not in self:
return "%s: Has unexpected key '%s'" % (self.label_str, k)
return None
def create_simple_prop(r):
if r[keys.PROPERTY_TYPE] not in resdef.list_of_simple_types:
raise SnapResDefError("The resdef type '%s' is not simple type" % r[keys.PROPERTY_TYPE])
simple_type = r[keys.PROPERTY_TYPE]
label_str = r[keys.LABEL]
description_str = r[keys.DESCRIPTION]
prop_constraint = r[keys.SIMPLE_PROPERTY_CONSTRAINT]
required = r[keys.REQUIRED]
simple_prop = SimpleProp(label_str, simple_type, description_str, prop_constraint, required)
return simple_prop
def create_list_prop(r):
if r[keys.PROPERTY_TYPE] != resdef.LIST_TYPE_PROPERTY:
raise SnapResDefError("The resdef type '%s' is not list property" % r[keys.PROPERTY_TYPE])
label_str = r[keys.LABEL]
description_str = r[keys.DESCRIPTION]
min_size = r[keys.COMPOSITE_PROPERTY_MIN_SIZE]
max_size = r[keys.COMPOSITE_PROPERTY_MAX_SIZE]
# Figure out default type
create_method = map_type_to_method[r[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE][keys.PROPERTY_TYPE]]
default_entry_type = create_method(r[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE])
required = r[keys.REQUIRED]
list_prop = ListProp(label_str, default_entry_type, description_str, min_size, max_size, required)
for entry in r[keys.COMPOSITE_PROPERTY_STRUCT]:
create_method = map_type_to_method[entry[keys.PROPERTY_TYPE]]
list_prop.append(create_method(entry))
return list_prop
def create_dict_prop(r):
if r[keys.PROPERTY_TYPE] != resdef.DICT_TYPE_PROPERTY:
raise SnapResDefError("The resdef type '%s' is not dictionary property" % r[keys.PROPERTY_TYPE])
label_str = r[keys.LABEL]
description_str = r[keys.DESCRIPTION]
min_size = r[keys.COMPOSITE_PROPERTY_MIN_SIZE]
max_size = r[keys.COMPOSITE_PROPERTY_MAX_SIZE]
keys_fixed = r[keys.FIXED_KEYS]
required = r[keys.REQUIRED]
# Figure out default type
create_method = map_type_to_method[r[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE][keys.PROPERTY_TYPE]]
default_entry_type = create_method(r[keys.COMPOSITE_PROPERTY_DEFAULT_ENTRY_TYPE])
dict_prop = DictProp(label_str, default_entry_type, description_str, min_size, max_size, keys_fixed, required)
composite_s = r[keys.COMPOSITE_PROPERTY_STRUCT]
for k in composite_s:
create_method = map_type_to_method[composite_s[k][keys.PROPERTY_TYPE]]
dict_prop[k] = create_method(composite_s[k])
return dict_prop
map_type_to_method = dict.fromkeys(resdef.list_of_simple_types, create_simple_prop)
map_type_to_method[resdef.LIST_TYPE_PROPERTY] = create_list_prop
map_type_to_method[resdef.DICT_TYPE_PROPERTY] = create_dict_prop
def create_property_from_resdef(r):
create_method = map_type_to_method[r[keys.PROPERTY_TYPE]]
return create_method(r)
def substitute_params_in_resdef(res_def, params):
"""
Takes param values and applies them to values of properties
@param res_def: The resource definition that needs param substitution
@type res_def: ResDef
@param params: Dictionary of param names and their values.
@type params: dict
"""
#
# Figure out the values of the properties
#
if params is not None and len(params) > 0:
for name in res_def.list_property_names():
value = res_def.get_property_value(name)
d = res_def.get_property_def(name)
prop_def = create_property_from_resdef(d[keys.PROPERTY_DEFINITION])
value = substitute_params_in_property(value, params, prop_def)
# Now we look for substituted properties
res_def.set_property_value(name, value)
def substitute_params_in_property(value, params, prop_def):
if (isinstance(prop_def, SimpleProp)):
if not resdef.is_str(value):
# This is not a string value, there is nothing to substitute here.
return value
vs = resdef.param_pattern.findall(value)
if len(vs) > 0:
not_substituted = None
# First do the substituition.
for v in vs:
p = v[3:-1]
t = params.get(p, None)
if t is None:
# A param value was not specified for p.
not_substituted = p
else:
value = value.replace(v,t)
# Next, convert the "fully" substituted value to expected type (like decimal, bool or detettime.
if not_substituted is None:
if prop_def.simple_type == "number":
try:
value = Decimal(value)
except Exception:
raise SnapException("Invalid param-substituted value '%s' for property '%s'" %
(value, prop_def.label_str))
elif prop_def.simple_type == "boolean":
b = value.lower()
if b == "true":
value = True
elif b == "false":
value = False
else:
raise SnapException("Invalid param-substituted value '%s' for property '%s'" %
(value, prop_def.label_str))
elif prop_def.simple_type == "datetime":
# We do not support datetime as yet.
raise SnapException("Datetime property '%s' cannot be parameterized." % prop_def.label_str)
err_str = prop_def.validate(value)
if err_str is not None:
raise SnapException("Property '%s' had validation error - %s" % (prop_def.label_str, err_str))
elif isinstance(prop_def, ListProp):
if value is not None:
if (not hasattr(value, "append")) or (not callable(value.append)):
raise SnapException("Value of property '%s' was not a list. Received: %s" %
(prop_def.label_str, value))
for (i, entry) in enumerate(value):
value[i] = substitute_params_in_property(entry, params, prop_def.get_item_type(i))
elif isinstance(prop_def, DictProp):
if value is not None:
if (not hasattr(value, "keys")) or (not callable(value.keys)):
raise SnapException("Value of property '%s' value was not a dictionary. Received: %s" %
(prop_def.label_str, value))
for k in value:
value[k] = substitute_params_in_property(value[k], params, prop_def.get_item_type(k))
return value
def validate_param_notations(param_names, prop_values, prop_def_dict, prop_err_obj):
"""
Validate that every param substitution notation (i.e. $?{}) in resdef refers to a defined param name.
@param param_names: Names of params defined for this resdef.
@type param_names: list
@param prop_def_dict: The dictionary of property name -> property definition dictionary
@type prop_def_dict: dict
@param prop_values: The dictionary of property name -> property values
@type prop_values: dict
@param prop_err_obj: Error object for the resdef being validated.
@type prop_err_obj: Error object.
"""
for name in prop_values:
prop_def = create_property_from_resdef(prop_def_dict[name][keys.PROPERTY_DEFINITION])
validate_param_notations_in_prop(param_names, prop_values[name], prop_def, prop_err_obj[name])
def validate_param_notations_in_prop(param_names, value, prop_def, err_obj):
"""
Validate that every param substitution notation (i.e. $?{}) in a property refers to a defined param name.
@param param_names: Names of params defined for this resdef.
@type param_names: list
@param value: The property value being inspected.
@type value: Any valid property value.
@param prop_def: The property definition corresponding to the value.
@type prop_def: L{SimpleProp}, L{ListProp}, L{DictProp}
@param err_obj: Error object for the property being validated. This object will be set with error
messages, if validation fails.
@type err_obj: Error object.
"""
if isinstance(prop_def, SimpleProp) and resdef.is_str(value):
vs = resdef.param_pattern.findall(value)
not_found = None
for v in vs:
p = v[3:-1]
if p not in param_names:
if not_found is None:
not_found = []
not_found.append(p)
if not_found:
err_obj.set_message("Resource definition has no parameter(s) named - %s" % ", ".join(not_found))
elif isinstance(prop_def, ListProp):
if value is not None:
if (not hasattr(value, "append")) or (not callable(value.append)):
# There are other issues with this value, don't dive down this branch of the tree.
return
for (i, entry) in enumerate(value):
if i >= len (err_obj):
# The index overshoots limits. Let the other validation code report it.
continue
validate_param_notations_in_prop(param_names, entry, prop_def.get_item_type(i), err_obj[i])
elif isinstance(prop_def, DictProp):
if value is not None:
if (not hasattr(value, "keys")) or (not callable(value.keys)):
# There are other issues with this value, don't dive down this branch of the tree.
return
for k in value:
if k not in err_obj:
# This must be an invalid key. Let the other validation code report it.
continue
validate_param_notations_in_prop(param_names, value[k], prop_def.get_item_type(k), err_obj[k])
|