Package gavo :: Package rscdef :: Module rmkfuncs
[frames] | no frames]

Source Code for Module gavo.rscdef.rmkfuncs

  1  """ 
  2  Functions available to rowmaker procs. 
  3   
  4  Rowmaker procs are compiled in the namespace defined by this module. 
  5   
  6  Maybe we should allow additional modules to be specified in gavorc? 
  7  """ 
  8   
  9  #c Copyright 2008-2019, the GAVO project 
 10  #c 
 11  #c This program is free software, covered by the GNU GPL.  See the 
 12  #c COPYING file in the source distribution. 
 13   
 14   
 15  import base64 
 16  import datetime 
 17  import math 
 18  import os 
 19  import pprint #noflake: exported name 
 20  import re #noflake: exported name 
 21  import sys #noflake: exported name 
 22  import time 
 23  import traceback #noflake: exported name 
 24  import urllib #noflake: exported name 
 25   
 26  from gavo import base 
 27  from gavo import stc 
 28  from gavo import utils 
 29  from gavo.base import ( #noflake: exported names 
 30          coords, parseBooleanLiteral, parseInt, sqlmunge, 
 31          makeSitePath, makeAbsoluteURL, SkipThis) 
 32  from gavo.base.literals import (identity, #noflake:exported names 
 33          parseInt, parseFloat, parseBooleanLiteral, parseUnicode, 
 34          parseDefaultDate, parseDefaultDatetime, parseDefaultTime, parseCooPair, 
 35          parseSPoint, getDefaultValueParsers) 
 36   
 37  from gavo.base.unitconv import ( #noflake: exported names 
 38          PLANCK_H, LIGHT_C) 
 39  from gavo.rscdef.common import ( #noflake: exported names 
 40          getStandardPubDID, getAccrefFromStandardPubDID, 
 41          getDatalinkMetaLink, 
 42          getInputsRelativePath) 
 43  from gavo.stc import parseSimpleSTCS #noflake: exported name 
 44  from gavo.stc.times import ( #noflake: exported names 
 45          dateTimeToJdn, dateTimeToMJD, dateTimeToJYear, 
 46          bYearToDateTime, jdnToDateTime, mjdToDateTime, TTtoTAI, TAItoTT) 
 47  from gavo.utils import codetricks 
 48  from gavo.utils import ( #noflake: exported names 
 49          dmsToDeg, hmsToDeg, DEG, parseISODT, iterSimpleText, getFileStem, 
 50          getWCSAxis, getRelativePath, loadPythonModule) 
 51  from gavo.utils import pgsphere #noflake: exported names 
 52   
 53   
 54  # degrees per mas 
 55  DEG_MAS = 1/3600000. 
 56  # degrees per arcsec 
 57  DEG_ARCSEC = 1/3600. 
 58   
 59   
 60  MS = base.makeStruct 
61 62 63 -class IgnoreThisRow(base.ExecutiveAction):
64 """can be raised by user code to indicate that a row should be 65 skipped when building a table. 66 67 Note: To skip an entire source, raise SkipThis (usually in a rowfilter 68 or so). 69 70 Also note that the non-code way to skip things, `Triggers`_, is 71 preferred when you don't already use code. 72 """
73
74 75 -def addCartesian(result, alpha, delta):
76 """inserts c_x, c_y, and c_z for the equatorial position alpha, delta 77 into result. 78 79 c_x, c_y, and c_z are the cartesian coordinates of the intersection 80 point of the radius vector of alpha, delta with the unit sphere. 81 82 alpha and delta already have to be floats, so you probably want 83 to use variables here. 84 85 >>> r = {}; addCartesian(r, 25, 30); str(r["c_x"]), str(r["c_y"]) 86 ('0.784885567221', '0.365998150771') 87 """ 88 result["c_x"], result["c_y"], result["c_z"] = coords.computeUnitSphereCoords( 89 alpha, delta)
90
91 92 -def combinePMs(result, pma, pmd):
93 """inserts pm_total (in degs/yr) and pm_posang (in degs, east over north) 94 into result. 95 96 pma and pmd have to be in degs/yr, with cos(delta) applied to pma. 97 """ 98 if pma is None or pmd is None: 99 tpm = pmpa = None 100 else: 101 tpm = math.sqrt(pma**2+pmd**2) 102 pmpa = math.atan2(pma, pmd)*360/2/math.pi 103 result["pm_total"] = tpm 104 result["pm_posang"] = pmpa
105
106 107 @utils.document 108 -def getQueryMeta():
109 """returns a query meta object from somewhere up the stack. 110 111 This is for row makers running within a service. This can be used 112 to, e.g., enforce match limits by writing getQueryMeta()["dbLimit"]. 113 """ 114 return codetricks.stealVar("queryMeta")
115
116 @utils.document 117 -def parseTime(literal, format="%H:%M:%S"):
118 """returns a ``datetime.timedelta`` object for literal parsed according 119 to format. 120 121 For format, you can the magic values ``!!secondsSinceMidnight``, 122 ``!!decimalHours`` or a strptime-like spec using the H, M, and S codes. 123 124 >>> parseTime("89930", "!!secondsSinceMidnight") 125 datetime.timedelta(1, 3530) 126 >>> parseTime("23.4", "!!decimalHours") 127 datetime.timedelta(0, 84240) 128 >>> parseTime("3.4:5", "%H.%M:%S") 129 datetime.timedelta(0, 11045) 130 >>> parseTime("20:04", "%H:%M") 131 datetime.timedelta(0, 72240) 132 """ 133 if format=="!!secondsSinceMidnight": 134 return datetime.timedelta(seconds=float(literal)) 135 elif format=="!!decimalHours": 136 return datetime.timedelta(hours=float(literal)) 137 else: 138 # We can't really use prebuilt strptimes since times like 25:21:22.445 139 # are perfectly ok in astronomy. 140 partDict = utils.parsePercentExpression(literal, format) 141 return datetime.timedelta(0, hours=float(partDict.get("H", 0)), 142 minutes=float(partDict.get("M", 0)), seconds=float(partDict.get("S", 0)))
143
144 145 @utils.document 146 -def parseDate(literal, format="%Y-%m-%d"):
147 """returns a ``datetime.date`` object of literal parsed according to the 148 strptime-similar format. 149 150 The function understands the special ``dateFormat`` ``!!jYear`` 151 (stuff like 1980.89). 152 """ 153 if format in ("!!julianEp", "!!jYear"): 154 return stc.jYearToDateTime(float(literal)) 155 return datetime.datetime(*time.strptime(literal, format)[:3])
156
157 158 @utils.document 159 -def parseTimestamp(literal, format="%Y-%m-%dT%H:%M:%S"):
160 """returns a ``datetime.datetime`` object from a literal parsed according 161 to the strptime-similar format. 162 163 A ``ValueError`` is raised if literal doesn't match format (actually, 164 a parse with essentially DALI-standard ISO representation is always tried) 165 """ 166 try: 167 return datetime.datetime(*time.strptime(literal, format)[:6]) 168 except ValueError: 169 # always try ISO format 170 return parseISODT(literal)
171
172 173 @utils.document 174 -def toMJD(literal):
175 """returns a modified julian date made from some datetime representation. 176 177 Valid representations include: 178 179 * MJD (a float smaller than 1e6) 180 * JD (a float larger than 1e6) 181 * datetime.datetime instances 182 * ISO time strings. 183 """ 184 if literal is None: 185 return None 186 elif isinstance(literal, str): 187 return stc.dateTimeToMJD(parseTimestamp(literal)) 188 elif isinstance(literal, datetime.datetime): 189 return stc.dateTimeToMJD(literal) 190 elif literal>1e6: 191 return literal-stc.JD_MJD 192 else: 193 return literal
194
195 196 @utils.document 197 -def makeTimestamp(date, time):
198 """makes a datetime instance from a date and a time. 199 """ 200 return datetime.datetime(date.year, date.month, date.day)+time
201
202 203 @utils.document 204 -def parseAngle(literal, format, sepChar=None):
205 """converts the various forms angles might be encountered to degrees. 206 207 format is one of hms, dms, fracHour. For sexagesimal/time angles, 208 you can pass a sepChar (default: split at blanks) that lets you 209 specify what separates hours/degrees, minutes, and seconds. 210 211 >>> str(parseAngle("23 59 59.95", "hms")) 212 '359.999791667' 213 >>> "%10.5f"%parseAngle("-20:31:05.12", "dms", sepChar=":") 214 ' -20.51809' 215 >>> "%010.6f"%parseAngle("21.0209556", "fracHour") 216 '315.314334' 217 """ 218 if format=="dms": 219 return utils.dmsToDeg(literal, sepChar=sepChar) 220 elif format=="hms": 221 return utils.hmsToDeg(literal, sepChar=sepChar) 222 elif format=="fracHour": 223 return utils.fracHoursToDeg(literal) 224 else: 225 raise base.Error("Invalid format: %s"%format)
226
227 228 @utils.document 229 -def computeMean(val1, val2):
230 """returns the mean value between two values. 231 232 Beware: Integer division done here for the benefit of datetime calculations. 233 234 >>> computeMean(1.,3) 235 2.0 236 >>> computeMean(datetime.datetime(2000, 10, 13), 237 ... datetime.datetime(2000, 10, 12)) 238 datetime.datetime(2000, 10, 12, 12, 0) 239 """ 240 return val1+(val2-val1)/2
241
242 243 @utils.document 244 -def killBlanks(literal):
245 """returns the string literal with all blanks removed. 246 247 This is useful when numbers are formatted with blanks thrown in. 248 249 Nones are passed through. 250 """ 251 if literal is None: 252 return None 253 else: 254 return literal.replace(" ", "")
255
256 257 @utils.document 258 -def lastSourceElements(path, numElements):
259 """returns a path made up from the last ``numElements`` items in ``path``. 260 """ 261 newPath = [] 262 for i in range(int(numElements)): 263 path, part = os.path.split(path) 264 newPath.append(part) 265 newPath.reverse() 266 return os.path.join(*newPath)
267
268 269 @utils.document 270 -def scale(val, factor, offset=0):
271 """returns val*factor+offset if val is not None, None otherwise. 272 273 This is when you want to manipulate a numeric value that may be NULL. 274 It is a somewhat safer alternative to using nullExcs with scaled values. 275 """ 276 if val is None: 277 return None 278 return factor*val+offset
279
280 281 @utils.document 282 -def parseWithNull(literal, baseParser, nullLiteral=base.Undefined, 283 default=None, checker=None):
284 """returns default if literal is ``nullLiteral``, else 285 ``baseParser(literal)``. 286 287 If ``checker`` is non-None, it must be a callable returning ``True`` if its 288 argument is a null value. 289 290 ``nullLiteral`` is compared against the unprocessed literal (usually, a 291 string). The intended use is like this (but note that often, a 292 ``nullExcs`` attribute on a rowmaker ``map`` element is the more 293 elegant way: 294 295 >>> parseWithNull("8888.0", float, "8888") 296 8888.0 297 >>> print(parseWithNull("8888", float, "8888")) 298 None 299 >>> print(parseWithNull("N/A", int, "N/A")) 300 None 301 """ 302 if (nullLiteral is not base.Undefined and literal==nullLiteral 303 ) or literal is None: 304 return default 305 if checker is not None: 306 if checker(literal): 307 return default 308 res = baseParser(literal) 309 if res is None: 310 return default 311 return res
312
313 314 -def getHTTPPar(inputData, parser, single=False, forceUnique=False, 315 parName="?"):
316 """returns a parsed value from inputData. 317 318 Deprecated. Should no longer be necessary with 1.0-style input 319 processing where you have guaranteed multiplicities after the 320 contextGrammar, and contextGrammars are reasonably cheap to use. 321 322 inputData may be 323 324 * None -- the function will return None 325 * an empty list -- the function will return None 326 * a value other than a list -- as if it were a list of length 1 327 * a list -- the function will return a list of parsed items 328 329 This is of conveniently and robustly pulling out data from stuff coming 330 out of inputKeys without multiplicity. 331 332 If you pass single=True, you'll get exactly one value (or None). The 333 value will be the first one from a sequence. 334 335 If you pass forceUnique=True, a MultiplicityError will be raised if 336 inputData is longer than one. 337 """ 338 if inputData is None: 339 return None 340 if not isinstance(inputData, list): 341 inputData = [inputData] 342 if len(inputData)==0: 343 return None 344 345 if forceUnique and len(inputData)>1: 346 raise base.MultiplicityError( 347 "Inputs for this parameter must not have more than" 348 " one value; hovever, %s was passed in."%str(inputData), 349 colName=parName) 350 351 if single: 352 return parser(inputData[0]) 353 else: 354 return [parser(v) for v in inputData]
355
356 357 @utils.document 358 -def requireValue(val, fieldName):
359 """returns ``val`` unless it is ``None``, in which case a ``ValidationError`` 360 for ``fieldName`` will be raised. 361 """ 362 if val is None: 363 raise base.ValidationError("Value is required but was not provided", 364 fieldName) 365 return val
366
367 368 -def genLimitKeys(inputKey):
369 """yields _MAX and _MIN inputKeys from a single input key. 370 371 This also tries to sensibly fix descriptions and ucds. 372 This is mainly for datalink metaMakers; condDescs may use a 373 similar thing, but that's not exposed to RDs. 374 375 Don't use this function any more. It will go away soon. 376 """ 377 name = inputKey.name 378 ucd = inputKey.ucd or None 379 description = inputKey.description 380 base.ui.notifyWarning("Deprecated genLimitKeys function used." 381 " Fix your RD to use proper DALI-intervals (ask dachs-support" 382 " if you don't know what to do.)") 383 384 yield inputKey.change(name=name+"_MIN", 385 ucd=ucd and "par.min;"+ucd, 386 description=description.rstrip(".")+", lower limit") 387 yield inputKey.change(name=name+"_MAX", 388 ucd=ucd and "par.max;"+ucd, 389 description=description.rstrip(".")+", upper limit")
390
391 392 @utils.document 393 -def getFlatName(accref):
394 """returns a unix-compatible file name for an access reference. 395 396 The file name will not contain terrible characters, let alone 397 slashes. This is used to, e.g., keep all previews in one directory. 398 """ 399 # just in case someone passes in a full file path, strip it 400 if accref.startswith("/"): 401 accref = utils.getRelativePath(accref, 402 base.getConfig("inputsDir"), liberalChars=True) 403 404 return base64.b64encode(accref, "$!")
405
406 407 -def addProcDefObject(name, func):
408 globals()[name] = func
409
410 411 -def makeProc(funcName, code, setupCode, parent, **moreNames):
412 """compiles a function in the rmkfunc's namespace. 413 414 code is a complete function source. setupCode is executed right away 415 in this namespace to add globals. 416 """ 417 funcNs = globals().copy() 418 funcNs["parent"] = parent 419 funcNs.update(moreNames) 420 if setupCode.strip(): 421 try: 422 exec setupCode.rstrip() in funcNs 423 except (SyntaxError, TypeError) as ex: 424 raise base.ui.logOldExc( 425 base.BadCode(setupCode, "setup code", ex)) 426 except NameError as ex: 427 raise base.ui.logOldExc( 428 base.BadCode(setupCode, "setup code", ex, 429 hint="This typically happens when you forget to put" 430 " quotes around string values.")) 431 return utils.compileFunction(code.rstrip(), funcName, funcNs, 432 debug=base.DEBUG)
433
434 435 -def _test():
436 import doctest, rmkfuncs 437 doctest.testmod(rmkfuncs)
438 439 440 if __name__=="__main__": 441 _test() 442