Show More
@@ -25,9 +25,9 b' Authors:' | |||||
25 | from unittest import TestCase |
|
25 | from unittest import TestCase | |
26 |
|
26 | |||
27 | from IPython.utils.traitlets import ( |
|
27 | from IPython.utils.traitlets import ( | |
28 | HasTraits, MetaHasTraits, TraitType, Any, |
|
28 | HasTraits, MetaHasTraits, TraitType, Any, CStr, | |
29 | Int, Long, Float, Complex, Str, Unicode, TraitError, |
|
29 | Int, Long, Float, Complex, Str, Unicode, TraitError, | |
30 | Undefined, Type, This, Instance, TCPAddress |
|
30 | Undefined, Type, This, Instance, TCPAddress, List, Tuple | |
31 | ) |
|
31 | ) | |
32 |
|
32 | |||
33 |
|
33 | |||
@@ -741,3 +741,74 b' class TestTCPAddress(TraitTestBase):' | |||||
741 | _default_value = ('127.0.0.1',0) |
|
741 | _default_value = ('127.0.0.1',0) | |
742 | _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)] |
|
742 | _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)] | |
743 | _bad_values = [(0,0),('localhost',10.0),('localhost',-1)] |
|
743 | _bad_values = [(0,0),('localhost',10.0),('localhost',-1)] | |
|
744 | ||||
|
745 | class ListTrait(HasTraits): | |||
|
746 | ||||
|
747 | value = List(Int) | |||
|
748 | ||||
|
749 | class TestList(TraitTestBase): | |||
|
750 | ||||
|
751 | obj = ListTrait() | |||
|
752 | ||||
|
753 | _default_value = [] | |||
|
754 | _good_values = [[], [1], range(10)] | |||
|
755 | _bad_values = [10, [1,'a'], 'a', (1,2)] | |||
|
756 | ||||
|
757 | class LenListTrait(HasTraits): | |||
|
758 | ||||
|
759 | value = List(Int, [0], minlen=1, maxlen=2) | |||
|
760 | ||||
|
761 | class TestLenList(TraitTestBase): | |||
|
762 | ||||
|
763 | obj = LenListTrait() | |||
|
764 | ||||
|
765 | _default_value = [0] | |||
|
766 | _good_values = [[1], range(2)] | |||
|
767 | _bad_values = [10, [1,'a'], 'a', (1,2), [], range(3)] | |||
|
768 | ||||
|
769 | class TupleTrait(HasTraits): | |||
|
770 | ||||
|
771 | value = Tuple(Int) | |||
|
772 | ||||
|
773 | class TestTupleTrait(TraitTestBase): | |||
|
774 | ||||
|
775 | obj = TupleTrait() | |||
|
776 | ||||
|
777 | _default_value = None | |||
|
778 | _good_values = [(1,), None,(0,)] | |||
|
779 | _bad_values = [10, (1,2), [1],('a'), ()] | |||
|
780 | ||||
|
781 | def test_invalid_args(self): | |||
|
782 | self.assertRaises(TypeError, Tuple, 5) | |||
|
783 | self.assertRaises(TypeError, Tuple, default_value='hello') | |||
|
784 | t = Tuple(Int, CStr, default_value=(1,5)) | |||
|
785 | ||||
|
786 | class LooseTupleTrait(HasTraits): | |||
|
787 | ||||
|
788 | value = Tuple((1,2,3)) | |||
|
789 | ||||
|
790 | class TestLooseTupleTrait(TraitTestBase): | |||
|
791 | ||||
|
792 | obj = LooseTupleTrait() | |||
|
793 | ||||
|
794 | _default_value = (1,2,3) | |||
|
795 | _good_values = [(1,), None, (0,), tuple(range(5)), tuple('hello'), ('a',5), ()] | |||
|
796 | _bad_values = [10, 'hello', [1], []] | |||
|
797 | ||||
|
798 | def test_invalid_args(self): | |||
|
799 | self.assertRaises(TypeError, Tuple, 5) | |||
|
800 | self.assertRaises(TypeError, Tuple, default_value='hello') | |||
|
801 | t = Tuple(Int, CStr, default_value=(1,5)) | |||
|
802 | ||||
|
803 | ||||
|
804 | class MultiTupleTrait(HasTraits): | |||
|
805 | ||||
|
806 | value = Tuple(Int, Str, default_value=[99,'bottles']) | |||
|
807 | ||||
|
808 | class TestMultiTuple(TraitTestBase): | |||
|
809 | ||||
|
810 | obj = MultiTupleTrait() | |||
|
811 | ||||
|
812 | _default_value = (99,'bottles') | |||
|
813 | _good_values = [(1,'a'), (2,'b')] | |||
|
814 | _bad_values = ((),10, 'a', (1,'a',3), ('a',1)) |
@@ -328,7 +328,7 b' class TraitType(object):' | |||||
328 | self.info(), repr_type(value)) |
|
328 | self.info(), repr_type(value)) | |
329 | else: |
|
329 | else: | |
330 | e = "The '%s' trait must be %s, but a value of %r was specified." \ |
|
330 | e = "The '%s' trait must be %s, but a value of %r was specified." \ | |
331 |
% (self.name, self.info(), repr_type(value)) |
|
331 | % (self.name, self.info(), repr_type(value)) | |
332 | raise TraitError(e) |
|
332 | raise TraitError(e) | |
333 |
|
333 | |||
334 | def get_metadata(self, key): |
|
334 | def get_metadata(self, key): | |
@@ -621,7 +621,15 b' class ClassBasedTraitType(TraitType):' | |||||
621 | else: |
|
621 | else: | |
622 | msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) |
|
622 | msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) ) | |
623 |
|
623 | |||
624 | super(ClassBasedTraitType, self).error(obj, msg) |
|
624 | if obj is not None: | |
|
625 | e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \ | |||
|
626 | % (self.name, class_of(obj), | |||
|
627 | self.info(), msg) | |||
|
628 | else: | |||
|
629 | e = "The '%s' trait must be %s, but a value of %r was specified." \ | |||
|
630 | % (self.name, self.info(), msg) | |||
|
631 | ||||
|
632 | raise TraitError(e) | |||
625 |
|
633 | |||
626 |
|
634 | |||
627 | class Type(ClassBasedTraitType): |
|
635 | class Type(ClassBasedTraitType): | |
@@ -1055,46 +1063,255 b' class CaselessStrEnum(Enum):' | |||||
1055 | return v |
|
1063 | return v | |
1056 | self.error(obj, value) |
|
1064 | self.error(obj, value) | |
1057 |
|
1065 | |||
|
1066 | class Container(Instance): | |||
|
1067 | """An instance of a container (list, set, etc.) | |||
1058 |
|
|
1068 | ||
1059 | class List(Instance): |
|
1069 | To be subclassed by overriding klass. | |
1060 | """An instance of a Python list.""" |
|
1070 | """ | |
|
1071 | klass = None | |||
|
1072 | _valid_defaults = SequenceTypes | |||
|
1073 | _trait = None | |||
1061 |
|
1074 | |||
1062 |
def __init__(self, default_value=None, allow_none=True, |
|
1075 | def __init__(self, trait=None, default_value=None, allow_none=True, | |
1063 | """Create a list trait type from a list, set, or tuple. |
|
1076 | **metadata): | |
|
1077 | """Create a container trait type from a list, set, or tuple. | |||
1064 |
|
1078 | |||
1065 |
The default value is created by doing `` |
|
1079 | The default value is created by doing ``List(default_value)``, | |
1066 | which creates a copy of the ``default_value``. |
|
1080 | which creates a copy of the ``default_value``. | |
|
1081 | ||||
|
1082 | ``trait`` can be specified, which restricts the type of elements | |||
|
1083 | in the container to that TraitType. | |||
|
1084 | ||||
|
1085 | If only one arg is given and it is not a Trait, it is taken as | |||
|
1086 | ``default_value``: | |||
|
1087 | ||||
|
1088 | ``c = List([1,2,3])`` | |||
|
1089 | ||||
|
1090 | Parameters | |||
|
1091 | ---------- | |||
|
1092 | ||||
|
1093 | trait : TraitType [ optional ] | |||
|
1094 | the type for restricting the contents of the Container. If unspecified, | |||
|
1095 | types are not checked. | |||
|
1096 | ||||
|
1097 | default_value : SequenceType [ optional ] | |||
|
1098 | The default value for the Trait. Must be list/tuple/set, and | |||
|
1099 | will be cast to the container type. | |||
|
1100 | ||||
|
1101 | allow_none : Bool [ default True ] | |||
|
1102 | Whether to allow the value to be None | |||
|
1103 | ||||
|
1104 | **metadata : any | |||
|
1105 | further keys for extensions to the Trait (e.g. config) | |||
|
1106 | ||||
1067 | """ |
|
1107 | """ | |
|
1108 | istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType) | |||
|
1109 | ||||
|
1110 | # allow List([values]): | |||
|
1111 | if default_value is None and not istrait(trait): | |||
|
1112 | default_value = trait | |||
|
1113 | trait = None | |||
|
1114 | ||||
1068 | if default_value is None: |
|
1115 | if default_value is None: | |
1069 |
args = ( |
|
1116 | args = () | |
1070 |
elif isinstance(default_value, |
|
1117 | elif isinstance(default_value, self._valid_defaults): | |
1071 | args = (default_value,) |
|
1118 | args = (default_value,) | |
1072 | else: |
|
1119 | else: | |
1073 |
raise TypeError('default value of |
|
1120 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) | |
|
1121 | ||||
|
1122 | if istrait(trait): | |||
|
1123 | self._trait = trait() | |||
|
1124 | self._trait.name = 'element' | |||
|
1125 | elif trait is not None: | |||
|
1126 | raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait)) | |||
1074 |
|
1127 | |||
1075 |
super( |
|
1128 | super(Container,self).__init__(klass=self.klass, args=args, | |
1076 | allow_none=allow_none, **metadata) |
|
1129 | allow_none=allow_none, **metadata) | |
1077 |
|
1130 | |||
|
1131 | def element_error(self, obj, element, validator): | |||
|
1132 | e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \ | |||
|
1133 | % (self.name, class_of(obj), validator.info(), repr_type(element)) | |||
|
1134 | raise TraitError(e) | |||
1078 |
|
1135 | |||
1079 | class Set(Instance): |
|
1136 | def validate(self, obj, value): | |
1080 | """An instance of a Python set.""" |
|
1137 | value = super(Container, self).validate(obj, value) | |
|
1138 | if value is None: | |||
|
1139 | return value | |||
1081 |
|
1140 | |||
1082 | def __init__(self, default_value=None, allow_none=True, **metadata): |
|
1141 | value = self.validate_elements(obj, value) | |
1083 | """Create a set trait type from a set, list, or tuple. |
|
1142 | ||
|
1143 | return value | |||
|
1144 | ||||
|
1145 | def validate_elements(self, obj, value): | |||
|
1146 | validated = [] | |||
|
1147 | if self._trait is None or isinstance(self._trait, Any): | |||
|
1148 | return value | |||
|
1149 | for v in value: | |||
|
1150 | try: | |||
|
1151 | v = self._trait.validate(obj, v) | |||
|
1152 | except TraitError: | |||
|
1153 | self.element_error(obj, v, self._trait) | |||
|
1154 | else: | |||
|
1155 | validated.append(v) | |||
|
1156 | return self.klass(validated) | |||
|
1157 | ||||
|
1158 | ||||
|
1159 | class List(Container): | |||
|
1160 | """An instance of a Python list.""" | |||
|
1161 | klass = list | |||
1084 |
|
1162 | |||
1085 | The default value is created by doing ``set(default_value)``, |
|
1163 | def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxint, | |
|
1164 | allow_none=True, **metadata): | |||
|
1165 | """Create a List trait type from a list, set, or tuple. | |||
|
1166 | ||||
|
1167 | The default value is created by doing ``List(default_value)``, | |||
1086 | which creates a copy of the ``default_value``. |
|
1168 | which creates a copy of the ``default_value``. | |
|
1169 | ||||
|
1170 | ``trait`` can be specified, which restricts the type of elements | |||
|
1171 | in the container to that TraitType. | |||
|
1172 | ||||
|
1173 | If only one arg is given and it is not a Trait, it is taken as | |||
|
1174 | ``default_value``: | |||
|
1175 | ||||
|
1176 | ``c = List([1,2,3])`` | |||
|
1177 | ||||
|
1178 | Parameters | |||
|
1179 | ---------- | |||
|
1180 | ||||
|
1181 | trait : TraitType [ optional ] | |||
|
1182 | the type for restricting the contents of the Container. If unspecified, | |||
|
1183 | types are not checked. | |||
|
1184 | ||||
|
1185 | default_value : SequenceType [ optional ] | |||
|
1186 | The default value for the Trait. Must be list/tuple/set, and | |||
|
1187 | will be cast to the container type. | |||
|
1188 | ||||
|
1189 | minlen : Int [ default 0 ] | |||
|
1190 | The minimum length of the input list | |||
|
1191 | ||||
|
1192 | maxlen : Int [ default sys.maxint ] | |||
|
1193 | The maximum length of the input list | |||
|
1194 | ||||
|
1195 | allow_none : Bool [ default True ] | |||
|
1196 | Whether to allow the value to be None | |||
|
1197 | ||||
|
1198 | **metadata : any | |||
|
1199 | further keys for extensions to the Trait (e.g. config) | |||
|
1200 | ||||
1087 | """ |
|
1201 | """ | |
|
1202 | self._minlen = minlen | |||
|
1203 | self._maxlen = maxlen | |||
|
1204 | super(List, self).__init__(trait=trait, default_value=default_value, | |||
|
1205 | allow_none=allow_none, **metadata) | |||
|
1206 | ||||
|
1207 | def length_error(self, obj, value): | |||
|
1208 | e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \ | |||
|
1209 | % (self.name, class_of(obj), self._minlen, self._maxlen, value) | |||
|
1210 | raise TraitError(e) | |||
|
1211 | ||||
|
1212 | def validate_elements(self, obj, value): | |||
|
1213 | length = len(value) | |||
|
1214 | if length < self._minlen or length > self._maxlen: | |||
|
1215 | self.length_error(obj, value) | |||
|
1216 | ||||
|
1217 | return super(List, self).validate_elements(obj, value) | |||
|
1218 | ||||
|
1219 | ||||
|
1220 | class Set(Container): | |||
|
1221 | """An instance of a Python set.""" | |||
|
1222 | klass = set | |||
|
1223 | ||||
|
1224 | class Tuple(Container): | |||
|
1225 | """An instance of a Python tuple.""" | |||
|
1226 | klass = tuple | |||
|
1227 | ||||
|
1228 | def __init__(self, *traits, **metadata): | |||
|
1229 | """Tuple(*traits, default_value=None, allow_none=True, **medatata) | |||
|
1230 | ||||
|
1231 | Create a tuple from a list, set, or tuple. | |||
|
1232 | ||||
|
1233 | Create a fixed-type tuple with Traits: | |||
|
1234 | ||||
|
1235 | ``t = Tuple(Int, Str, CStr)`` | |||
|
1236 | ||||
|
1237 | would be length 3, with Int,Str,CStr for each element. | |||
|
1238 | ||||
|
1239 | If only one arg is given and it is not a Trait, it is taken as | |||
|
1240 | default_value: | |||
|
1241 | ||||
|
1242 | ``t = Tuple((1,2,3))`` | |||
|
1243 | ||||
|
1244 | Otherwise, ``default_value`` *must* be specified by keyword. | |||
|
1245 | ||||
|
1246 | Parameters | |||
|
1247 | ---------- | |||
|
1248 | ||||
|
1249 | *traits : TraitTypes [ optional ] | |||
|
1250 | the tsype for restricting the contents of the Tuple. If unspecified, | |||
|
1251 | types are not checked. If specified, then each positional argument | |||
|
1252 | corresponds to an element of the tuple. Tuples defined with traits | |||
|
1253 | are of fixed length. | |||
|
1254 | ||||
|
1255 | default_value : SequenceType [ optional ] | |||
|
1256 | The default value for the Tuple. Must be list/tuple/set, and | |||
|
1257 | will be cast to a tuple. If `traits` are specified, the | |||
|
1258 | `default_value` must conform to the shape and type they specify. | |||
|
1259 | ||||
|
1260 | allow_none : Bool [ default True ] | |||
|
1261 | Whether to allow the value to be None | |||
|
1262 | ||||
|
1263 | **metadata : any | |||
|
1264 | further keys for extensions to the Trait (e.g. config) | |||
|
1265 | ||||
|
1266 | """ | |||
|
1267 | default_value = metadata.pop('default_value', None) | |||
|
1268 | allow_none = metadata.pop('allow_none', True) | |||
|
1269 | ||||
|
1270 | istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType) | |||
|
1271 | ||||
|
1272 | # allow Tuple((values,)): | |||
|
1273 | if len(traits) == 1 and default_value is None and not istrait(traits[0]): | |||
|
1274 | default_value = traits[0] | |||
|
1275 | traits = () | |||
|
1276 | ||||
1088 | if default_value is None: |
|
1277 | if default_value is None: | |
1089 |
args = ( |
|
1278 | args = () | |
1090 |
elif isinstance(default_value, |
|
1279 | elif isinstance(default_value, self._valid_defaults): | |
1091 | args = (default_value,) |
|
1280 | args = (default_value,) | |
1092 | else: |
|
1281 | else: | |
1093 |
raise TypeError('default value of |
|
1282 | raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value)) | |
1094 |
|
1283 | |||
1095 | super(Set,self).__init__(klass=set, args=args, |
|
1284 | self._traits = [] | |
|
1285 | for trait in traits: | |||
|
1286 | t = trait() | |||
|
1287 | t.name = 'element' | |||
|
1288 | self._traits.append(t) | |||
|
1289 | ||||
|
1290 | if self._traits and default_value is None: | |||
|
1291 | # don't allow default to be an empty container if length is specified | |||
|
1292 | args = None | |||
|
1293 | super(Container,self).__init__(klass=self.klass, args=args, | |||
1096 | allow_none=allow_none, **metadata) |
|
1294 | allow_none=allow_none, **metadata) | |
1097 |
|
1295 | |||
|
1296 | def validate_elements(self, obj, value): | |||
|
1297 | if not self._traits: | |||
|
1298 | # nothing to validate | |||
|
1299 | return value | |||
|
1300 | if len(value) != len(self._traits): | |||
|
1301 | e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \ | |||
|
1302 | % (self.name, class_of(obj), len(self._traits), repr_type(value)) | |||
|
1303 | raise TraitError(e) | |||
|
1304 | ||||
|
1305 | validated = [] | |||
|
1306 | for t,v in zip(self._traits, value): | |||
|
1307 | try: | |||
|
1308 | v = t.validate(obj, v) | |||
|
1309 | except TraitError: | |||
|
1310 | self.element_error(obj, v, t) | |||
|
1311 | else: | |||
|
1312 | validated.append(v) | |||
|
1313 | return tuple(validated) | |||
|
1314 | ||||
1098 |
|
1315 | |||
1099 | class Dict(Instance): |
|
1316 | class Dict(Instance): | |
1100 | """An instance of a Python dict.""" |
|
1317 | """An instance of a Python dict.""" | |
@@ -1117,7 +1334,6 b' class Dict(Instance):' | |||||
1117 | super(Dict,self).__init__(klass=dict, args=args, |
|
1334 | super(Dict,self).__init__(klass=dict, args=args, | |
1118 | allow_none=allow_none, **metadata) |
|
1335 | allow_none=allow_none, **metadata) | |
1119 |
|
1336 | |||
1120 |
|
||||
1121 | class TCPAddress(TraitType): |
|
1337 | class TCPAddress(TraitType): | |
1122 | """A trait for an (ip, port) tuple. |
|
1338 | """A trait for an (ip, port) tuple. | |
1123 |
|
1339 |
General Comments 0
You need to be logged in to leave comments.
Login now