Attachment 'Formatter.py'

Download

   1 #	Formatter.py
   2 #
   3 #	------------------------------------------------------------
   4 #	Copyright 2002, 2004 by Samuel Reynolds. All rights reserved.
   5 #
   6 #	Permission to use, copy, modify, and distribute this software and its
   7 #	documentation for any purpose and without fee is hereby granted,
   8 #	provided that the above copyright notice appear in all copies and that
   9 #	both that copyright notice and this permission notice appear in
  10 #	supporting documentation, and that the name of Samuel Reynolds
  11 #	not be used in advertising or publicity pertaining to distribution
  12 #	of the software without specific, written prior permission.
  13 #
  14 #	SAMUEL REYNOLDS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
  15 #	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
  16 #	EVENT SHALL SAMUEL REYNOLDS BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
  17 #	CONSEQUENTIAL DAMAGES, OR FOR ANY DAMAGES WHATSOEVER RESULTING FROM
  18 #	LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  19 #	NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  20 #	WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  21 #	------------------------------------------------------------
  22 
  23 
  24 """
  25 Formatters for converting and validating data values.
  26 """
  27 
  28 import copy, re, time
  29 
  30 class Formatter( object ):
  31 	"""
  32 	Formatter/validator for data values.
  33 	"""
  34 	def __init__( self, *args, **kwargs ):
  35 		pass
  36 	
  37 	# Default (dummy) validate routine
  38 	def validate( self, value ):
  39 		"""
  40 		Return true if value is valid for the field.
  41 		value is a string from the UI.
  42 		"""
  43 		return True
  44 	
  45 	# Default (dummy) format routine
  46 	def format( self, value ):
  47 		"""Format a value for presentation in the UI."""
  48 		if value == None:
  49 			return ''
  50 		return str(value)
  51 	
  52 	# Default (dummy) coerce routine
  53 	def coerce( self, value ):
  54 		"""Convert a string from the UI into a storable value."""
  55 		return value
  56 
  57 
  58 class EnumFormatter( Formatter ):
  59 	"""
  60 	Formatter for enumerated (EnumType) data values.
  61 	"""
  62 	def __init__( self, enumeration, *args, **kwargs ):
  63 		
  64 		super(EnumFormatter,self).__init__( *args, **kwargs )
  65 		
  66 		self.enumeration = enumeration
  67 	
  68 	
  69 	def validValues( self ):
  70 		"""
  71 		Return list of valid value (id,label) pairs.
  72 		"""
  73 		return copy.copy( self.enumeration.items() )
  74 	
  75 	
  76 	def validate( self, value ):
  77 		"""
  78 		Return true if value is valid for the field.
  79 		value is a string from the UI.
  80 		"""
  81 		vv = [ s for i, s in self.validValues() ]
  82 		return ( value in vv )
  83 	
  84 	
  85 	def format( self, value ):
  86 		"""Format a value for presentation in the UI."""
  87 		return self.enumeration[value]
  88 	
  89 	
  90 	def coerce( self, value ):
  91 		"""Convert a string from the UI into a storable value."""
  92 		return getattr( self.enumeration, value )
  93 
  94 
  95 class FormatterMeta( type ):
  96 	"""
  97 	Metaclass for subclasses of Formatter.
  98 	
  99 	Each instance class MUST define either validate(self, value) method
 100 	or re_validation regular expression string.
 101 	
 102 	If the latter, the validate method will be autogenerated.
 103 	
 104 	If re_validation is defined, validate method is overridden to
 105 	validate the string value against the regular expression.
 106 	
 107 	If re_validation_flags is defined, the flags will be
 108 	used when the re_validation regular expression string is compiled.
 109 	
 110 	Each instance class MAY define:
 111 	-	format(self, value) method.
 112 	-	coerce(self, value) method.
 113 	"""
 114 	def __new__( cls, classname, bases, classdict ):
 115 		newdict = copy.copy( classdict )
 116 		
 117 		# Generate __init__ method
 118 		# Direct descendants of Formatter automatically get __init__.
 119 		# Indirect descendants don't automatically get one.
 120 		if Formatter in bases:
 121 			def __init__( self, *args, **kwargs ):
 122 				Formatter.__init__( self, *args, **kwargs )
 123 				initialize = getattr( self, 'initialize', None )
 124 				if initialize:
 125 					initialize()
 126 			newdict['__init__'] = __init__
 127 		else:
 128 			def __init__( self, *args, **kwargs ):
 129 				super(self.__class__,self).__init__( *args, **kwargs)
 130 				initialize = getattr( self, 'initialize', None )
 131 				if initialize:
 132 					initialize()
 133 			newdict['__init__'] = __init__
 134 		
 135 		# Generate validate-by-RE method if specified
 136 		re_validation = newdict.get( 're_validation', None )
 137 		if re_validation:
 138 			# Override validate method
 139 			re_validation_flags = newdict.get( 're_validation_flags', 0 )
 140 			newdict['_re_validation'] = re.compile( re_validation, re_validation_flags )
 141 			def validate( self, value ):
 142 				return ( self._re_validation.match( value ) != None )
 143 			newdict['validate'] = validate
 144 		
 145 		# Delegate class creation to the expert
 146 		return type.__new__( cls, classname, bases, newdict )
 147 
 148 
 149 class ObjectIdFormatter( Formatter ):
 150 	"""
 151 	Object ID is assumed to be a large (32 bit?) unsigned integer.
 152 	"""
 153 	__metaclass__ = FormatterMeta
 154 	re_validation = '^[0-9]+$'
 155 	def coerce( self, value ):
 156 		if value: return long(value)
 157 		return value
 158 
 159 class StringFormatter( Formatter ):
 160 	__metaclass__ = FormatterMeta
 161 
 162 class AlphaFormatter( StringFormatter ):
 163 	"""Alphabetic characters only."""
 164 	re_validation = '^[a-zA-Z]*$'
 165 
 166 class AlphaNumericFormatter( StringFormatter ):
 167 	"""Alphanumeric characters only."""
 168 	re_validation = '^[a-zA-Z0-9]*$'
 169 
 170 class EmailFormatter( StringFormatter ):
 171 	"""Internet email addresses (more or less)."""
 172 	# This regex does not match all legal email addresses, but
 173 	# it does a pretty good job.
 174 	# Strangely enough, '/' is legal in email addresses.
 175 	# However, I've never seen it used, so I prefer to leave it out.
 176 	_re_subs = {
 177 			'sub1' : r'[a-zA-Z~_-][a-zA-Z0-9_:~-]*',
 178 			'sub2' : r'(\.[a-zA-Z0-9_:~-]+)*',
 179 			'sfx'  : r'\.[a-zA-Z]{2,3}'
 180 		}
 181 	re_validation = '^%(sub1)s%(sub2)s[@]%(sub1)s%(sub2)s%(sfx)s$' % _re_subs
 182 
 183 class MoneyFormatter( StringFormatter ):
 184 	"""Assumes decimal money, but doesn't assume currency."""
 185 	re_validation = '^(([0-9]+([.][0-9]{2})?)|([0-9]*[.][0-9]{2}))$'
 186 
 187 class IntFormatter( Formatter ):
 188 	"""Signed or unsigned integer."""
 189 	__metaclass__ = FormatterMeta
 190 	#re_validation = '^[-+]?[0-9]+$'
 191 	def validate( self, value ):
 192 		try:
 193 			v = int( value )
 194 			return True
 195 		except:
 196 			return False
 197 	def coerce( self, value ):
 198 		if value: return int(value)
 199 		return value
 200 
 201 class Int8Formatter( IntFormatter ):
 202 	pass
 203 
 204 class Int16Formatter( IntFormatter ):
 205 	pass
 206 
 207 class Int24Formatter( IntFormatter ):
 208 	def coerce( self, value ):
 209 		if value: return long(value)
 210 		return value
 211 
 212 class Int32Formatter( IntFormatter ):
 213 	def coerce( self, value ):
 214 		if value: return long(value)
 215 		return value
 216 
 217 class Int64Formatter( IntFormatter ):
 218 	def coerce( self, value ):
 219 		if value: return long(value)
 220 		return value
 221 
 222 class UIntFormatter( Formatter ):
 223 	"""Unsigned integer."""
 224 	__metaclass__ = FormatterMeta
 225 	re_validation = '^[0-9]+$'
 226 	def coerce( self, value ):
 227 		if value: return int(value)
 228 		return value
 229 
 230 class UInt8Formatter( UIntFormatter ):
 231 	pass
 232 
 233 class UInt16Formatter( UIntFormatter ):
 234 	def coerce( self, value ):
 235 		if value: return long(value)
 236 		return value
 237 
 238 class UInt24Formatter( UIntFormatter ):
 239 	def coerce( self, value ):
 240 		if value: return long(value)
 241 		return value
 242 
 243 class UInt32Formatter( UIntFormatter ):
 244 	def coerce( self, value ):
 245 		if value: return long(value)
 246 		return value
 247 
 248 class FloatFormatter( Formatter ):
 249 	"""Signed or unsigned floating-point number."""
 250 	__metaclass__ = FormatterMeta
 251 	re_validation = '^[-+]?(([0-9]+[.]?[0-9]*)|([0-9]*[.]?[0-9]+))$'
 252 	def coerce( self, value ):
 253 		if value: return float(value)
 254 		return value
 255 
 256 class DoubleFormatter( FloatFormatter ):
 257 	pass
 258 
 259 class UFloatFormatter( Formatter ):
 260 	"""Unsigned floating-point number."""
 261 	__metaclass__ = FormatterMeta
 262 	re_validation = '^(([0-9]+[.]?[0-9]*)|([0-9]*[.]?[0-9]+))$'
 263 	def coerce( self, value ):
 264 		if value: return float(value)
 265 		return value
 266 
 267 class UDoubleFormatter( UFloatFormatter ):
 268 	pass
 269 
 270 
 271 class TextFormatter( Formatter ):
 272 	__metaclass__ = FormatterMeta
 273 
 274 
 275 class TimeElapsedFormatter( Formatter ):
 276 	"""Elapsed time string (HH:MM:SS)."""
 277 	__metaclass__ = FormatterMeta
 278 	re_validation = '^([1][0-2]|[0]?[0-9]):[0-5][0-9](:[0-5][0-9])?$'
 279 
 280 class DateFormatter( Formatter ):
 281 	"""
 282 	Date string (YYYY-MM-DD).
 283 	
 284 	Storage format:      YYYY-MM-DD
 285 	Presentation format: YYYY-MM-DD
 286 	
 287 	Accepts only YYYY-MM-DD format (allows variant separators '/' and '.').
 288 	Accepts dates in range (1000-2999)-(01-12)-(01-31).
 289 	Leading zeros optional in month and day.
 290 	Does not enforce # of days in month.
 291 	"""
 292 	__metaclass__ = FormatterMeta
 293 	
 294 	re_validation = r'^[1-2][0-9]{3}([-/.])([0][1-9]|[1][0-2])\1([0][1-9]|[12][0-9]|[3][0-1])$'
 295 	
 296 	def coerce( self, value ):
 297 		"""Convert alternate date separators to '-'."""
 298 		return re.sub( r'[/.]', '-', value )
 299 
 300 class DateFormatterMDY( DateFormatter ):
 301 	"""Alternate date string (MM-DD-YYYY).
 302 	
 303 	Storage format:      YYYY-MM-DD
 304 	Presentation format: MM-DD-YYYY
 305 	
 306 	Accepts only MM-DD-YYYY format  (allows variant separators '/' and '.').
 307 	Accepts dates in range (01-12)-(01-31)-(1000-2999).
 308 	Leading zeros optional in month and day.
 309 	Does not enforce # of days in month.
 310 	"""
 311 	re_validation = r'^([0][1-9]|[1][0-2])([-/.])([0][1-9]|[12][0-9]|[3][0-1])\1[12][0-9]{3}$'
 312 
 313 	def format( self, value ):
 314 		dt = time.strptime( value, '%Y-%m-%d' )
 315 		return time.strftime( '%m-%d-%Y', dt )
 316 	
 317 	def coerce( self, value ):
 318 #		value = re.sub( r'[/.]', '-', value )
 319 #		dt = time.strptime( value, '%m-%d-%Y' )
 320 #		return time.strftime( '%Y-%m-%d', dt )
 321 		m, d, y = re.split( '[-/.]', value )
 322 		return '%04d-%02d-%02d' % ( int(y), int(m), int(d) )
 323 
 324 class TimeFormatter( Formatter ):
 325 	"""
 326 	Time string (12-hour or 24-hour format, with or without seconds or am/pm).
 327 	
 328 	Storage format:      HH:MM:SS -- 24-hour format.
 329 	Presentation format: HH:MM    -- 24-hour format.
 330 	
 331 	Accepts 12-hour or 24-hour format, with or without seconds or am/pm.
 332 	"""
 333 	__metaclass__ = FormatterMeta
 334 	
 335 	reTime24 = r'(([0]?[0-9]|[1][0-9]|[2][0-3]):[0-5][0-9](:[0-5][0-9])?)'
 336 	reTimeAP = r'(([1][0-2]|[0]?[0-9]):[0-5][0-9](:[0-5][0-9])?[ ]*([aApP][mM])?)'
 337 	re_validation = r'^%s|%s$' % ( reTime24, reTimeAP )
 338 	
 339 	def format( self, value ):
 340 		return ':'.join( value.split(':')[:2] )
 341 	
 342 	def coerce( self, value ):
 343 		for fmt in ( '%H:%M:%S', '%H:%M', '%I:%M:%S %p', '%I:%M %p','%I:%M:%S' ):
 344 			try:
 345 				dt = time.strptime( value, fmt )
 346 				break
 347 			except ValueError:
 348 				pass
 349 		return time.strftime( '%H:%M:%S', dt )
 350 
 351 class TimeFormatter12H( TimeFormatter ):
 352 	"""
 353 	Alternate time string (12-hour format, without seconds).
 354 	
 355 	Storage format:      HH:MM(:SS) -- 24-hour format.
 356 	Presentation format: HH:MM( aa) -- 12-hour format.
 357 	                     (aa may be 'am' or 'pm')
 358 	"""
 359 	
 360 	def format( self, value ):
 361 		dt = time.strptime( value, '%H:%M:%S' )
 362 		return time.strftime( '%I:%M %p', dt )
 363 
 364 class DateTimeFormatter( Formatter ):
 365 	"""
 366 	Date/time string.
 367 	
 368 	Uses a DateFormatter and a TimeFormatter.
 369 	"""
 370 	__metaclass__ = FormatterMeta
 371 	
 372 	# Storage format: YYYY-MM-DD HH:MM:SS -- 24-hour format.
 373 	# Presentation format: same as storage format.
 374 	
 375 	def validate( self , value ):
 376 		datef = DateFormatter()
 377 		timef = TimeFormatter()
 378 		date, time = re.split( r'[ ]+', value )
 379 		return ( datef.validate( date ) and timef.validate( time ) )
 380 
 381 
 382 if __name__ == '__main__':
 383 	
 384 	from EnumType import EnumType
 385 	
 386 #	from prs.PRSObject import PRSObject
 387 #	
 388 #	print 'dir(ObjectIdField) =', dir(ObjectIdField)
 389 #	print 'ObjectIdField.__init__ =', ObjectIdField.__init__
 390 #	print 'ObjectIdField.__init__.func_doc =', ObjectIdField.__init__.func_doc
 391 #	anIdField = ObjectIdFormatter( False )
 392 #	print 'dir(anIdField) =', dir(anIdField)
 393 #	print 'anIdField.__class__ =', anIdField.__class__
 394 #	
 395 #	print
 396 #	print 'dir(DateFormatterMDY) =', dir(DateFormatterMDY)
 397 #	print
 398 	
 399 	# ========== EnumFormatter ==========
 400 	print
 401 	print 'EnumFormatter'
 402 	TestEnum = EnumType( 'A', 'B', 'C', 'D' )
 403 	formatter = EnumFormatter( TestEnum )
 404 	passCount = 0
 405 	failCount = 0
 406 	
 407 	# formatter.validValues
 408 	expectedValidValues = [ (0,'A'), (1,'B'), (2,'C'), (3,'D') ]
 409 	temp = formatter.validValues()
 410 	if temp == expectedValidValues:
 411 		passCount += 1
 412 	else:
 413 		failCount += 1
 414 		print 'formatter.validValues() = %r -- WRONG -- expected %r' % (temp, expectedValidValues )
 415 	
 416 	# formatter.format and formatter.coerce
 417 	for id, name in TestEnum.items():
 418 		# formatter.format
 419 		temp = formatter.format( id )
 420 		if temp == name:
 421 			passCount += 1
 422 		else:
 423 			failCount += 1
 424 			print 'formatter.format(%d) = %r -- WRONG -- expected %r' % (id, temp, name )
 425 		# formatter.coerce
 426 		temp = formatter.coerce( name )
 427 		if temp == id:
 428 			passCount += 1
 429 		else:
 430 			failCount += 1
 431 			print 'formatter.coerce(%r) = %r -- WRONG -- expected %r' % (name, temp, id )
 432 	if failCount == 0:
 433 		print '\tPASS (%d tests)' % passCount
 434 	
 435 	
 436 	# ========== EmailFormatter ==========
 437 	print
 438 	print 'EmailFormatter'
 439 	
 440 	tests = [
 441 			( 'a@b.com', True ),
 442 			( 'a@b', False ),
 443 			( '@b', False ),
 444 			( '@b.c', False ),
 445 			( '@b.us', False ),
 446 			( 'a@b.us', True ),
 447 			( '~joe_public-73@bozo.net', True ),
 448 		]
 449 	formatter = EmailFormatter()
 450 	passCount = 0
 451 	failCount = 0
 452 	
 453 	for email, expect in tests:
 454 		actual = formatter.validate( email )
 455 		if actual == expect:
 456 			passCount += 1
 457 		else:
 458 			failCount += 1
 459 	if failCount == 0:
 460 		print '\tPASS (%d tests)' % passCount

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2009-10-04 09:16:02, 13.1 KB) [[attachment:Formatter.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.