Commits

Jason R. Coombs committed aa3a7ed

Updated RangeMap implementation with version from jaraco.util 5.3.1 that includes a fix for RangeMap.get. This fixes the failing tests, including the issue identified by 3521185.

  • Participants
  • Parent commits 0d04c35

Comments (0)

Files changed (1)

win32/Lib/win32timezone.py

 		>>> tzi = TimeZoneInfo('Central Standard Time')
 
 		Here's how the RangeMap is supposed to work:
-		>>> m = RangeMap(zip([2006,2007], 'BC'), descending, operator.ge)
+		>>> m = RangeMap(zip([2006,2007], 'BC'),
+		...     sort_params = dict(reverse=True),
+		...     key_match_comparator=operator.ge)
 		>>> m.get(2000, 'A')
 		'A'
 		>>> m[2006]
 		>>> m[2008]
 		'C'
 
-		>>> m[RangeItemLast()]
+		>>> m[RangeMap.last_item]
 		'B'
 
-		>>> m.get(2008, m[RangeItemLast()])
+		>>> m.get(2008, m[RangeMap.last_item])
 		'C'
 
 
 		values = map(TimeZoneDefinition, info.values())
 		# create a range mapping that searches by descending year and matches
 		# if the target year is greater or equal.
-		self.dynamicInfo = RangeMap(zip(years, values), descending, operator.ge)
+		self.dynamicInfo = RangeMap(zip(years, values),
+			sort_params = dict(reverse=True),
+			key_match_comparator = operator.ge)
 
 	def __repr__(self):
 		result = '%s(%s' % (self.__class__.__name__, repr(self.timeZoneName))
 		#  a year greater than or equal to our targetYear. If not found,
 		#  default to the earliest year.
 		return self.dynamicInfo.get(targetYear,
-			self.dynamicInfo[RangeItemLast()])
+			self.dynamicInfo[RangeMap.last_item])
 
 	def _getStandardBias(self, dt):
 		winInfo = self.getWinInfo(dt.year)
 		result = None
 	return result
 
-# the following code implements a RangeMap and its support classes
+# from jaraco.util.dictlib 5.3.1
+class RangeMap(dict):
+	"""
+	A dictionary-like object that uses the keys as bounds for a range.
+	Inclusion of the value for that range is determined by the
+	key_match_comparator, which defaults to less-than-or-equal.
+	A value is returned for a key if it is the first key that matches in
+	the sorted list of keys.
 
-ascending = lambda a, b: b < a
-def descending(a, b):
-	return not ascending(a, b)
-
-class RangeMap(dict):
-	"""A dictionary-like object that uses the keys as bounds for a range.
-	Inclusion of the value for that range is determined by the
-	keyMatchComparator, which defaults to greater-than-or-equal.
-	A value is returned for a key if it is the first key that matches in
-	the sorted list of keys.  By default, keys are sorted in ascending
-	order, but can be sorted in any other order using the keySortComparator.
+	One may supply keyword parameters to be passed to the sort function used
+	to sort keys (i.e. cmp [python 2 only], keys, reverse) as sort_params.
 
 	Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
 	>>> r = RangeMap({3: 'a', 6: 'b'})  # boy, that was easy
 	>>> r[1], r[2], r[3], r[4], r[5], r[6]
 	('a', 'a', 'a', 'b', 'b', 'b')
 
+	Even float values should work so long as the comparison operator
+	supports it.
+	>>> r[4.5]
+	'b'
+
 	But you'll notice that the way rangemap is defined, it must be open-ended on one side.
 	>>> r[0]
 	'a'
 	>>> r[-1]
 	'a'
 
-	One can close the open-end of the RangeMap by using RangeValueUndefined
-	>>> r = RangeMap({0: RangeValueUndefined(), 3: 'a', 6: 'b'})
+	One can close the open-end of the RangeMap by using undefined_value
+	>>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
 	>>> r[0]
 	Traceback (most recent call last):
 	...
 	KeyError: 0
 
-	One can get the first or last elements in the range by using RangeItem
-	>>> last_item = RangeItem(-1)
+	One can get the first or last elements in the range by using RangeMap.Item
+	>>> last_item = RangeMap.Item(-1)
 	>>> r[last_item]
 	'b'
 
-	>>> r[RangeItemLast()]
+	.last_item is a shortcut for Item(-1)
+	>>> r[RangeMap.last_item]
 	'b'
 
+	Sometimes it's useful to find the bounds for a RangeMap
 	>>> r.bounds()
 	(0, 6)
 
+	RangeMap supports .get(key, default)
+	>>> r.get(0, 'not found')
+	'not found'
+
+	>>> r.get(7, 'not found')
+	'not found'
+
 	"""
-	def __init__(self, source, keySortComparator = ascending, keyMatchComparator = operator.le):
+	def __init__(self, source, sort_params = {}, key_match_comparator = operator.le):
 		dict.__init__(self, source)
-		self.sort = keySortComparator
-		self.match = keyMatchComparator
-
-	def _get_sorted_keys(self):
-		sortedKeys = self.keys()
-		reverse = self.match != operator.le
-		sortedKeys.sort(reverse=reverse)
-		return sortedKeys
+		self.sort_params = sort_params
+		self.match = key_match_comparator
 
 	def __getitem__(self, item):
-		sortedKeys = self._get_sorted_keys()
-		if isinstance(item, RangeItem):
-			result = self.__getitem__(sortedKeys[item])
+		sorted_keys = sorted(self.keys(), **self.sort_params)
+		if isinstance(item, RangeMap.Item):
+			result = self.__getitem__(sorted_keys[item])
 		else:
-			key = self._find_first_match_(sortedKeys, item)
+			key = self._find_first_match_(sorted_keys, item)
 			result = dict.__getitem__(self, key)
-			if isinstance(result, RangeValueUndefined): raise KeyError(key)
+			if result is RangeMap.undefined_value:
+				raise KeyError(key)
 		return result
 
+	def get(self, key, default=None):
+		"""
+		Return the value for key if key is in the dictionary, else default.
+		If default is not given, it defaults to None, so that this method
+		never raises a KeyError.
+		"""
+		try:
+			return self[key]
+		except KeyError:
+			return default
+
 	def _find_first_match_(self, keys, item):
 		is_match = lambda k: self.match(item, k)
-		# use of ifilter here would be more efficent
-		matches = [k for k in keys if is_match(k)]
+		matches = list(filter(is_match, keys))
 		if matches:
 			return matches[0]
 		raise KeyError(item)
 
 	def bounds(self):
-		sortedKeys = self._get_sorted_keys()
-		return sortedKeys[RangeItemFirst()], sortedKeys[RangeItemLast()]
+		sorted_keys = sorted(self.keys(), **self.sort_params)
+		return (
+			sorted_keys[RangeMap.first_item],
+			sorted_keys[RangeMap.last_item],
+		)
 
-class RangeValueUndefined(object): pass
-class RangeItem(int):
-	def __new__(cls, value):
-		return int.__new__(cls, value)
-class RangeItemFirst(RangeItem):
-	def __new__(cls):
-		return RangeItem.__new__(cls, 0)
-class RangeItemLast(RangeItem):
-	def __new__(cls):
-		return RangeItem.__new__(cls, -1)
+	# some special values for the RangeMap
+	undefined_value = type(str('RangeValueUndefined'), (object,), {})()
+	class Item(int): pass
+	first_item = Item(0)
+	last_item = Item(-1)