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
13
14
15
16
17
18 import re
19
20
21
22
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
29 from gavo.utils import (parseDefaultDatetime,
30 parseDefaultDate, parseDefaultTime)
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")
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"])
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
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
97 if literal is None:
98 return
99 if isinstance(literal, str):
100 literal = literal.decode("ascii", "replace")
101 return unicode(literal)
102
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
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
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
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)
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
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