"""
open/dulcinea/lib/property/property_type.py
"""
from dulcinea.base import DulcineaPersistent
from dulcinea.material import Material, get_material_db
from dulcinea.physical_unit import get_standard_units
from dulcinea.physical_unit import PhysicalUnit, get_standard_unit
from dulcinea.physical_value import PhysicalValue
from dulcinea.property import get_property_db
from dulcinea.property.errors import PropertyTypeError, ConstraintError
from dulcinea.range_value import RangeValue
from dulcinea.set_utils import in_set
from durus.persistent import PersistentObject
from durus.persistent_dict import PersistentDict
from qp.lib.spec import mapping, anything, instance, spec, require, string
from qp.lib.spec import Specified
from qpy import xml, xml_quote, stringify
import dulcinea.property.property_template
class PropertyType (DulcineaPersistent):
INT = 1
STRING = 2
PHYSICAL_VALUE = 3
MATERIAL = 4
BOOLEAN = 5
ATOMIC_TYPES = ((INT, "integer"),
(STRING, "string"),
(PHYSICAL_VALUE, "physical value"),
(MATERIAL, "material"),
(BOOLEAN, 'boolean'))
ATOMIC = 10
LIST = 20
TABLE = 30
AGGREGATE_TYPES = ((ATOMIC, "atomic"),
(LIST, "list"),
(TABLE, "table"))
def is_atomic(self):
return 0
def is_int(self):
return 0
def is_string(self):
return 0
def is_physical_value(self):
return 0
def is_material(self):
return 0
def is_boolean(self):
return 0
def is_list(self):
return 0
def is_table(self):
return 0
def is_discrete(self, constraint):
raise NotImplementedError
def allow_tolerance(self):
return False
def get_unit(self):
return None
def get_compatible_units(self):
return None
def check_value(self, value):
"""Checks that the *type* of value is compatible
with this type of property.
Raise PropertyTypeError if problems are discovered.
"""
raise NotImplementedError
def supports_simplify(self):
return True
def supports_constraints(self):
"Returns true if setting constraints makes sense for this type."
return True
def check_constraint(self, constraint):
"""Checks that constraint is a sequence of
items whose types are compatible with this type
of property.
Raise PropertyTypeError if problems are discovered.
"""
if constraint is None:
return
if type(constraint) is list:
for item in constraint:
self.check_value(item)
else:
raise PropertyTypeError(
'invalid constraint %r for %r' % (constraint, self))
def check_value_in_constraint(self, value, constraint):
if not in_set(value, constraint):
self.value_in_constraint_error(value, constraint)
def check_set_in_constraint(self, set, constraint):
self.check_constraint(set)
if constraint is None or set == constraint:
return
if set is None:
raise ConstraintError
for element in set:
if not in_set(element, constraint):
raise ConstraintError(
'%s not within constraint %s' % (element, constraint))
def value_in_constraint_error(self, bad_value, constraint):
raise ConstraintError(
'%s not allowed: %s' % (bad_value,
self.explain_constraint(constraint)))
def explain_constraint(self, constraint, html=0):
if html:
texttype = xml
else:
texttype = str
if not constraint:
return None
else:
set = self.format_set(constraint, html=html)
if set is None:
return None
elif len(set) <= 2:
return texttype("must be ") + texttype(" or ").join(set)
else:
return texttype("must be one of ") + texttype(", ").join(set)
def value_error(self, value):
raise PropertyTypeError(
'invalid value %r for %r' % (value, self))
def format_value(self, value, html=0):
if html:
return xml_quote(value)
else:
return str(value)
def format_set(self, set, html=False):
"""(set:[any], html:bool=False) -> [str|xml]
Return a list of nice string (or xml) renderings of the elements
of set, which is a constraint for this property type.
Implemented in subclasses.
"""
raise NotImplementedError
def format_constraint(self, set, html=0):
if set is not None:
formatted_set = self.format_set(set, html=html)
if formatted_set is not None:
if html:
comma = xml(", ")
else:
comma = ", "
return comma.join(formatted_set)
if html:
return xml('unconstrained')
else:
return '(unconstrained)'
class IntPropertyType (PropertyType):
def get_key(self):
return (PropertyType.ATOMIC, PropertyType.INT,
None, None, None)
def is_atomic(self):
return 1
def is_int(self):
return 1
def is_discrete(self, constraint):
if constraint is None:
return False
for item in constraint:
if isinstance(item, RangeValue):
return False
return True
def __str__(self):
return "Int"
def check_value(self, value):
if value is None:
return
if type(value) is int:
return
if isinstance(value, RangeValue):
if type(value.get_min()) is int:
return
self.value_error(value)
def simplify_value(self, value):
return value
def reconstitute_value(self, value):
return value
def format_set(self, set, html=0):
if html:
return list(map(xml_quote, set))
else:
return list(map(str, set))
def get_constraint_range(self, constraint):
if constraint is None:
return None
def get_min_and_max (val):
if isinstance(val, RangeValue):
return val.get_min(), val.get_max()
else:
return val, val
val_min, val_max = get_min_and_max(constraint[0])
for value in constraint[1:]:
next_min, next_max = get_min_and_max(value)
val_min = min(val_min, next_min)
val_max = max(val_max, next_max)
return RangeValue(val_min, val_max)
class StringPropertyType (PropertyType):
def get_key(self):
return (PropertyType.ATOMIC, PropertyType.STRING,
None, None, None)
def is_atomic(self):
return 1
def is_string(self):
return 1
def is_discrete(self, constraint):
if constraint is None:
return False
else:
return True
def __str__(self):
return "String"
def check_value(self, value):
if value is None:
return
if string(value):
return
self.value_error(value)
def simplify_value(self, value):
return value
def reconstitute_value(self, value):
return value
def format_set(self, set, html=0):
return set
class BooleanPropertyType (PropertyType):
def get_key(self):
return (PropertyType.ATOMIC, PropertyType.BOOLEAN,
None, None, None)
def is_atomic(self):
return 1
def is_boolean(self):
return 1
def is_discrete(self, constraint):
return True
def __str__(self):
return "Boolean"
def check_value(self, value):
if value in (True, False, None):
return
self.value_error(value)
def simplify_value(self, value):
return value
def reconstitute_value(self, value):
return value
def supports_constraints(self):
return False
def format_value(self, value, html=0):
if value:
return 'yes'
else:
return 'no'
def format_constraint(self, set, html=0):
if html:
return xml('yes or no')
else:
return '(yes or no)'
class PhysicalValuePropertyType (PropertyType):
sub_type_is = (None, PhysicalUnit)
def __init__(self, sub_type):
assert sub_type is None or isinstance(sub_type, PhysicalUnit), (
'invalid sub_type %r for %r' % (sub_type, self))
self.sub_type = sub_type
def get_key(self):
return (PropertyType.ATOMIC, PropertyType.PHYSICAL_VALUE,
None, self.sub_type, None)
def get_unit(self):
return self.sub_type
def get_compatible_units(self):
unit = self.get_unit()
if not unit:
return []
return get_standard_units().get_compatible_units(unit)
def is_atomic(self):
return 1
def is_physical_value(self):
return 1
def is_discrete(self, constraint):
if constraint is None:
return False
for item in constraint:
if item.is_range():
return False
return True
def allow_tolerance(self):
return True
def __str__(self):
if self.sub_type is None:
return "PhysicalValue"
else:
return "PhysicalValue (%s)" % self.sub_type
def check_value(self, value):
if value is None:
return
if isinstance(value, PhysicalValue):
if self.sub_type is value.get_unit():
return
if self.sub_type and self.sub_type.is_compatible(value.get_unit()):
return
self.value_error(value)
def get_constraint_range(self, constraint):
if constraint is None:
return None
val_min = constraint[0].get_min()
val_max = constraint[0].get_max()
for value in constraint[1:]:
val_min = min(val_min, value.get_min())
val_max = max(val_max, value.get_max())
assert val_min.get_unit() is val_max.get_unit(), (
'unmatched units %r and %r for %r' % (val_min.get_unit(),
val_max.get_unit(),
self))
return PhysicalValue(RangeValue(val_min.get_value(),
val_max.get_value()),
val_min.get_unit())
def simplify_value(self, value):
default_unit = self.get_unit()
if value.unit is not default_unit:
value = value.convert(default_unit)
return value.value
def reconstitute_value(self, value):
if string(value) and value.find(' .. ') != -1:
value = RangeValue(*list(map(float, value.split(' .. ', 1))))
return PhysicalValue(value, self.get_unit())
def format_value(self, value, html=0):
if isinstance(value, PhysicalValue):
return value.format(html=html)
return stringify(value)
def format_set(self, set, html=0):
items = [value.format(show_unit=0, html=html)
for value in set[0:-1]]
items.append(set[-1].format(show_unit=1, html=html))
return items
class MaterialPropertyType (PropertyType):
def get_key(self):
return (PropertyType.ATOMIC, PropertyType.MATERIAL,
None, None, None)
def is_atomic(self):
return 1
def is_material(self):
return 1
def is_discrete(self, constraint):
return True
def __str__(self):
return "Material"
def check_value(self, value):
if (value is None or
isinstance(value, Material)):
return
self.value_error(value)
def check_value_in_constraint(self, value, constraint):
for material in constraint:
if value.is_descendant_of(material):
break
else:
self.value_in_constraint_error(value, constraint)
def simplify_value(self, value):
return value.get_name()
def reconstitute_value(self, value):
material = get_material_db().get_material(value)
if material is None:
raise PropertyTypeError('unknown material %r' % value)
return material
def format_set(self, set, html=0):
if len(set) == len(get_material_db().get_materials()):
return None
else:
return [value.get_label()
for value in set]
class ListPropertyType (PropertyType):
element_type_is = PropertyType
def __init__(self, element_type):
assert isinstance(element_type, PropertyType), (
'invalid element_type %r for %r' % (element_type, self))
self.element_type = element_type
def get_key(self):
element_key = self.element_type.get_key()
return (PropertyType.LIST, element_key[1],
None, element_key[3], None)
def is_int(self):
return self.element_type.is_int()
def is_string(self):
return self.element_type.is_string()
def is_physical_value(self):
return self.element_type.is_physical_value()
def get_compatible_units(self):
return self.element_type.get_compatible_units()
def get_unit(self):
return self.element_type.get_unit()
def is_material(self):
return self.element_type.is_material()
def is_list(self):
return 1
def is_discrete(self, constraint):
return self.element_type.is_discrete(constraint)
def __str__(self):
return "List of %s" % self.element_type
def check_value(self, value):
if value is None:
return
if type(value) is list:
for val in value:
self.element_type.check_value(val)
return
self.value_error(value)
def supports_simplify(self):
return False
def supports_constraints(self):
return self.element_type.supports_constraints()
def check_value_in_constraint(self, values, constraint):
for value in values:
self.element_type.check_value_in_constraint(value, constraint)
def check_constraint(self, constraint):
self.element_type.check_constraint(constraint)
def format_value(self, value, html=0):
fv = self.element_type.format_value
values_format = [fv(val, html=html) for val in value]
if html:
comma = xml(", ")
else:
comma = ", "
return comma.join(values_format)
def format_set(self, set, html=0):
return self.element_type.format_set(set, html=0)
class TablePropertyType (PropertyType):
key_type_is = PropertyType
element_type_is = PropertyType
def __init__(self, element_type, key_type):
assert isinstance(element_type, PropertyType), (
'invalid element_type %r for %r' % (element_type, self))
assert isinstance(key_type, PropertyType), (
'invalid key_type %r for %r' % (key_type, self))
self.element_type = element_type
self.key_type = key_type
def get_key(self):
element_key = self.element_type.get_key()
key_key = self.key_type.get_key()
return (PropertyType.TABLE, element_key[1],
key_key[1], element_key[3], key_key[3])
def is_int(self):
return self.element_type.is_int()
def is_string(self):
return self.element_type.is_string()
def is_physical_value(self):
return self.element_type.is_physical_value()
def get_compatible_units(self):
return (self.key_type.get_compatible_units() or
self.element_type.get_compatible_units())
def get_unit(self):
return self.element_type.get_unit()
def is_material(self):
return self.element_type.is_material()
def is_key_int(self):
return self.key_type.is_int()
def is_key_string(self):
return self.key_type.is_string()
def is_key_physical_value(self):
return self.key_type.is_physical_value()
def is_key_material(self):
return self.key_type.is_material()
def is_table(self):
return 1
def is_discrete(self, constraint):
assert constraint is None, 'cannot constrain %r' % self
return False
def get_key_type(self):
return self.key_type
def get_element_type(self):
return self.element_type
def __str__(self):
return "Table (%s to %s)" % (self.key_type, self.element_type)
def check_value(self, value):
if value is None:
return
if type(value) is dict:
for key, val in value.items():
self.key_type.check_value(key)
self.element_type.check_value(val)
return
self.value_error(value)
def supports_simplify(self):
return False
def supports_constraints(self):
"Returns true if setting constraints makes sense for this type."
return False
def check_value_in_constraint(self, value, constraint):
assert constraint is None, 'cannot constrain %r' % self
def check_constraint(self, constraint):
assert constraint is None, 'cannot constrain %r' % self
def format_value(self, value, html=0):
if html:
texttype = xml
else:
texttype = str
rows = [texttype("%s: %s") % (
self.key_type.format_value(key, html=html),
self.element_type.format_value(value, html=html))
for key, value in value.items()]
return texttype(", ").join(sorted(rows))
def create_atomic_type (element_type, element_sub_type=None):
if element_type == PropertyType.INT:
property_type = IntPropertyType()
elif element_type == PropertyType.STRING:
property_type = StringPropertyType()
elif element_type == PropertyType.PHYSICAL_VALUE:
property_type = PhysicalValuePropertyType(element_sub_type)
elif element_type == PropertyType.MATERIAL:
property_type = MaterialPropertyType()
elif element_type == PropertyType.BOOLEAN:
property_type = BooleanPropertyType()
else:
assert False, 'unknown atomic type %r' % element_type
return property_type
def get_property_type (*args, **kw):
return get_property_db().get_property_type(*args, **kw)
def get_physical_value_property_type (unit_string=None):
unit = unit_string and get_standard_unit(unit_string)
return get_property_type(PropertyType.ATOMIC,
PropertyType.PHYSICAL_VALUE,
element_sub_type=unit)
def get_list_of_physical_value_property_type (unit_string=None):
unit = unit_string and get_standard_unit(unit_string)
return get_property_type(PropertyType.LIST,
PropertyType.PHYSICAL_VALUE,
element_sub_type=unit)
def get_int_property_type():
return get_property_type(PropertyType.ATOMIC,
PropertyType.INT)
def get_list_of_int_property_type():
return get_property_type(PropertyType.LIST,
PropertyType.INT)
def get_string_property_type():
return get_property_type(PropertyType.ATOMIC,
PropertyType.STRING)
def get_list_of_string_property_type():
return get_property_type(PropertyType.LIST,
PropertyType.STRING)
def get_boolean_property_type():
return get_property_type(PropertyType.ATOMIC,
PropertyType.BOOLEAN)
def get_material_property_type():
return get_property_type(PropertyType.ATOMIC,
PropertyType.MATERIAL)
def get_list_of_material_property_type():
return get_property_type(PropertyType.LIST,
PropertyType.MATERIAL)
def get_string_int_table_property_type():
return get_property_type(PropertyType.TABLE,
PropertyType.INT,
key_type=PropertyType.STRING)
def get_physical_value_int_table_property_type (unit_string=None):
unit = unit_string and get_standard_unit(unit_string)
return get_property_type(PropertyType.TABLE,
element_type=PropertyType.INT,
key_type=PropertyType.PHYSICAL_VALUE,
key_sub_type=unit)
class PropertyDatabase (PersistentObject, Specified):
property_types_is = spec(
mapping({(int, int, (None, int), anything, anything): PropertyType},
PersistentDict),
"The first three elements of the tuple are constants, defined "
"in PropertyType that identify the aggregate type, the key "
"type, and the element type of this particular PropertyType. "
"The last two elements of the tuple identify subtypes of "
"the key and the element types. These subtypes are "
"usually instances of PhysicalUnit.")
master_templates_is = spec(
mapping({string:instance('MasterTemplate')}, PersistentDict),
"master templates by name")
def __init__(self):
self.property_types = PersistentDict()
self.master_templates = PersistentDict()
def add_master_template(self, master):
require(master, dulcinea.property.property_template.MasterTemplate)
if self.master_templates.has_key(master.get_name()):
raise ValueError(
"MasterTemplate '%s' is already defined." % master.get_name())
self.master_templates[master.get_name()] = master
def rename_master_template(self, master, new_name):
"""(master : MasterTemplate, new_name : string)
This should only be called by MasterTemplate.set_name()
"""
if not self.master_templates.has_key(master.get_name()):
raise ValueError(
"MasterTemplate '%s' is already defined." % master.get_name())
if self.master_templates.has_key(new_name):
raise ValueError(
"MasterTemplate '%s' is already defined." % master.get_name())
old_name = master.get_name()
master.set_name(new_name)
del self.master_templates[old_name]
self.master_templates[new_name] = master
def get_master_template(self, name):
return self.master_templates.get(name)
def get_master_templates(self):
return list(self.master_templates.values())
def _create_property_type(self,
aggregate_type,
element_type,
key_type=None,
element_sub_type=None,
key_sub_type=None):
assert aggregate_type is PropertyType.TABLE or (
key_type is None and key_sub_type is None), (
"invalid type creation: key_type %r or key_sub_type %r "
"defined for non table type" % (key_type, key_sub_type))
assert element_type is PropertyType.PHYSICAL_VALUE or (
element_sub_type is None), (
"invalid type creation: element_type %r does not take "
"sub_type argument %r" % (element_type, element_sub_type))
if aggregate_type is PropertyType.ATOMIC:
property_type = create_atomic_type(element_type,
element_sub_type)
elif aggregate_type is PropertyType.LIST:
element_inst = self.get_property_type(
PropertyType.ATOMIC, element_type,
element_sub_type=element_sub_type)
property_type = ListPropertyType(element_inst)
elif aggregate_type is PropertyType.TABLE:
assert key_type is PropertyType.PHYSICAL_VALUE or (
key_sub_type is None), (
"invalid type creation: key_type %r does not take "
"sub_type argument %r" % (key_type, key_sub_type))
element_inst = self.get_property_type(
PropertyType.ATOMIC, element_type,
element_sub_type=element_sub_type)
key_inst = self.get_property_type(
PropertyType.ATOMIC, key_type, element_sub_type=key_sub_type)
property_type = TablePropertyType(element_inst, key_inst)
else:
assert False, (
"unknown aggregate type: %r" % aggregate_type)
self.property_types[(aggregate_type,
element_type,
key_type,
element_sub_type,
key_sub_type)] = property_type
return property_type
def get_property_type(self,
aggregate_type,
element_type,
key_type=None,
element_sub_type=None,
key_sub_type=None):
property_type = self.property_types.get((aggregate_type,
element_type,
key_type,
element_sub_type,
key_sub_type))
if property_type is None:
property_type = self._create_property_type(aggregate_type,
element_type,
key_type,
element_sub_type,
key_sub_type)
return property_type