Package gavo :: Package base :: Module literals
[frames] | no frames]

Source Code for Module gavo.base.literals

  1  """ 
  2  Functions taking strings and returning python values. 
  3   
  4  All of them accept None and return None for Nullvalue processing. 
  5   
  6  All of them leave values alone if they already have the right type. 
  7   
  8  This is usually used in conjunction with  
  9  base.typesystems.ToPythonCodeConverter. 
 10  """ 
 11   
 12  #c Copyright 2008-2019, the GAVO project 
 13  #c 
 14  #c This program is free software, covered by the GNU GPL.  See the 
 15  #c COPYING file in the source distribution. 
 16   
 17   
 18  import re 
 19   
 20  # let's depend on psycopg2 for now as regards ranges.  We can always 
 21  # provide a thin shim here if we want to use different databases. 
 22  # Advise users to only use base.NumericRange, though. 
 23  from psycopg2.extras import NumericRange 
 24   
 25  from gavo import utils 
 26  from gavo.stc import parseSimpleSTCS 
 27  from gavo.utils import pgsphere 
 28  from gavo.utils import identity #noflake: exported name 
 29  from gavo.utils import (parseDefaultDatetime,  #noflake: exported names 
 30          parseDefaultDate, parseDefaultTime) 
31 32 @utils.document 33 -def parseInt(literal):
34 """returns an int from a literal, or None if literal is None or an empty 35 string. 36 37 >>> parseInt("32") 38 32 39 >>> parseInt("") 40 >>> parseInt(None) 41 """ 42 if literal is None or (isinstance(literal, basestring 43 ) and not literal.strip()): 44 return 45 return int(literal)
46 47 48 _inf = float("Inf")
49 @utils.document 50 -def parseFloat(literal):
51 """returns a float from a literal, or None if literal is None or an empty 52 string. 53 54 Temporarily, this includes a hack to work around a bug in psycopg2. 55 56 >>> parseFloat(" 5e9 ") 57 5000000000.0 58 >>> parseFloat(None) 59 >>> parseFloat(" ") 60 >>> parseFloat("wobbadobba") 61 Traceback (most recent call last): 62 ValueError: could not convert string to float: wobbadobba 63 """ 64 if (literal is None or 65 (isinstance(literal, basestring) and not literal.strip())): 66 return None 67 res = float(literal) 68 return res
69 70 _trueLiterals = set(["true", "yes", "t", "on", "enabled", "1"]) 71 _falseLiterals = set(["false", "no", "f", "off", "disabled", "0"])
72 73 @utils.document 74 -def parseBooleanLiteral(literal):
75 """returns a python boolean from some string. 76 77 Boolean literals are strings like True, false, on, Off, yes, No in 78 some capitalization. 79 """ 80 if literal is None or isinstance(literal, bool): 81 return literal 82 if hasattr(literal, "item"): 83 # numpy _bool 84 return literal.item() 85 86 literal = literal.lower() 87 if literal in _trueLiterals: 88 return True 89 elif literal in _falseLiterals: 90 return False 91 else: 92 raise ValueError( 93 "'%s' is no recognized boolean literal."%literal)
94
95 96 -def parseUnicode(literal):
97 if literal is None: 98 return 99 if isinstance(literal, str): 100 literal = literal.decode("ascii", "replace") 101 return unicode(literal)
102
103 104 -def parseCooPair(soup):
105 """returns a pair of RA, DEC floats if they can be made out in soup 106 or raises a value error. 107 108 No range checking is done (yet), i.e., as long as two numbers can be 109 made out, the function is happy. 110 111 >>> parseCooPair("23 12") 112 (23.0, 12.0) 113 >>> parseCooPair("23.5,-12.25") 114 (23.5, -12.25) 115 >>> parseCooPair("3.75 -12.125") 116 (3.75, -12.125) 117 >>> parseCooPair("3 25,-12 30") 118 (51.25, -12.5) 119 >>> map(str, parseCooPair("12 15 30.5 +52 18 27.5")) 120 ['183.877083333', '52.3076388889'] 121 >>> parseCooPair("3.39 -12 39") 122 Traceback (most recent call last): 123 ValueError: Invalid time with sepChar None: '3.39' 124 >>> parseCooPair("12 15 30.5 +52 18 27.5e") 125 Traceback (most recent call last): 126 ValueError: 12 15 30.5 +52 18 27.5e has no discernible position in it 127 >>> parseCooPair("QSO2230+44.3") 128 Traceback (most recent call last): 129 ValueError: QSO2230+44.3 has no discernible position in it 130 """ 131 soup = soup.strip() 132 133 def parseFloatPair(soup): 134 mat = re.match("(%s)\s*[\s,/]\s*(%s)$"%(utils.floatRE, 135 utils.floatRE), soup) 136 if mat: 137 return float(mat.group(1)), float(mat.group(2))
138 139 def parseSexa(soup): 140 timeangleRE = r"(?:\d+\s+)?(?:\d+\s+)?\d+(?:\.\d*)?" 141 dmsRE = "[+-]?\s*(?:\d+\s+)?(?:\d+\s+)?\d+(?:\.\d*)?" 142 mat = re.match("(%s)\s*[\s,/]?\s*(%s)$"%(timeangleRE, dmsRE), soup) 143 if mat: 144 try: 145 return utils.hmsToDeg(mat.group(1)), utils.dmsToDeg( 146 mat.group(2)) 147 except utils.Error as msg: 148 raise utils.logOldExc(ValueError(str(msg))) 149 150 def parseSexaColon(soup): 151 timeangleRE = r"(?:\d+:)?(?:\d+:)?\d+(?:\.\d*)?" 152 dmsRE = "[+-]?\s*(?:\d+:)?(?:\d+:)?\d+(?:\.\d*)?" 153 mat = re.match("(%s)\s*[\s,/]?\s*(%s)$"%(timeangleRE, dmsRE), soup) 154 if mat: 155 try: 156 return (utils.hmsToDeg(mat.group(1), sepChar=":"), 157 utils.dmsToDeg(mat.group(2), sepChar=":")) 158 except utils.Error as msg: 159 raise utils.logOldExc(ValueError(str(msg))) 160 161 for func in [parseFloatPair, parseSexa, parseSexaColon]: 162 res = func(soup) 163 if res: 164 return res 165 raise ValueError("%s has no discernible position in it"%soup) 166
167 168 -def parseSPoint(soup):
169 """returns an ``SPoint`` for a coordinate pair. 170 171 The coordinate pair can be formatted in a variety of ways; see the 172 `function parseCooPair`_. Input is always in degrees. 173 """ 174 if soup is None or isinstance(soup, pgsphere.SPoint): 175 return soup 176 return pgsphere.SPoint.fromDegrees(*parseCooPair(soup))
177
178 179 -def parseInterval(soup):
180 """tries to parse a numeric interval out of soup. 181 182 Technically, we expect a space-separated pair of numeric somethings. 183 If a part can be parsed as an int is, else it becomes a float. 184 185 What's returned is a NumericRange object (currently from psycopg2, but 186 you should only import NumericRange from base). 187 188 Invalid literals raise some sort of ValueError. 189 190 >>> parseInterval('3 4') 191 NumericRange(3, 4, '[)') 192 >>> parseInterval('3.5 4.75') 193 NumericRange(3.5, 4.75, '[)') 194 >>> parseInterval('20') 195 Traceback (most recent call last): 196 ValueError: Not a valid numeric interval literal: '20' 197 >>> parseInterval('gabba gubbu') 198 Traceback (most recent call last): 199 ValueError: could not convert string to float: gabba 200 """ 201 try: 202 lower, upper = soup.split() 203 except ValueError: 204 raise ValueError("Not a valid numeric interval literal: %s"%repr(soup)) 205 206 try: 207 lower = int(lower) 208 except ValueError: 209 lower = float(lower) 210 try: 211 upper = int(upper) 212 except ValueError: 213 upper = float(upper) 214 215 return NumericRange(lower, upper)
216
217 218 -def _numericRangeFactory(colDesc):
219 """A factory to serialise psycopg numeric ranges into VOTables. 220 221 If what's coming in is a NumericRange, it's turned into a 2-tuple; 222 else it's left alone. 223 224 (as of 1.2, DaCHS can't really read the result again properly) 225 """ 226 if colDesc["xtype"]=="interval" and colDesc["datatype"] in [ 227 "int", "long", "float", "double"]: 228 229 def mapper(val): 230 if val is None: 231 return None 232 if isinstance(val, NumericRange): 233 if val.upper_inc and colDesc["datatype"] in ["int", "long"]: 234 return (val.lower, val.upper+1) 235 return (val.lower, val.upper) 236 return val
237 238 return mapper 239 240 utils.registerDefaultMF(_numericRangeFactory)
241 242 243 @utils.memoized 244 -def getDefaultValueParsers():
245 """returns a dict containing all exported names from this module. 246 247 This is useful with typesystems.ToPythonCodeConverter; see 248 rscdef.column.Parameter for an example. 249 250 This is always the same dict; thus, if you change it, copy it first. 251 """ 252 all = set(__all__) 253 return dict((n,v) for n,v in globals().iteritems() if n in all)
254
255 256 -def _test():
257 import doctest, literals 258 doctest.testmod(literals)
259 260 261 if __name__=="__main__": 262 _test() 263 264 265 __all__ = ["parseInt", "parseFloat", "parseBooleanLiteral", "parseUnicode", 266 "parseDefaultDate", "parseDefaultTime", "parseDefaultDatetime", 267 "parseCooPair", "getDefaultValueParsers", "parseSPoint", "parseSimpleSTCS", 268 "NumericRange"] 269