1 """
2 A caching proxy for CDS' Simbad object resolver.
3 """
4
5
6
7
8
9
10
11 from __future__ import print_function
12
13 import cPickle
14 import os
15 import socket
16 import tempfile
17 import urllib
18
19 from gavo import base
20 from gavo import utils
21 from gavo.utils import ElementTree
28 self.id = id
29 self._loadCache()
30
32 return os.path.join(base.getConfig("cacheDir"), "oc"+self.id)
33
35 try:
36 self.cache = cPickle.load(open(self._getCacheName()))
37 except IOError:
38 self.cache = {}
39
41 try:
42 handle, name = tempfile.mkstemp(dir=base.getConfig("cacheDir"))
43 f = os.fdopen(handle, "w")
44 cPickle.dump(self.cache, f)
45 utils.safeclose(f)
46 os.rename(name, self._getCacheName())
47 except (IOError, os.error):
48 if not silent:
49 raise
50
51 - def addItem(self, key, record, save, silent=False):
55
57 self._saveCache(silent=True)
58
61
64 """is a simple interface to the simbad name resolver.
65 """
66 SVC_URL = "http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-ox/SN?"
67
68 - def __init__(self, id="simbad", debug=False, saveNew=False):
69 self.saveNew = saveNew
70 self.debug = debug
71 self._getCache(id)
72
75
77 try:
78 et = ElementTree.fromstring(simbadXML)
79 except Exception as msg:
80 base.ui.notifyWarning("Bad XML from simbad (%s)"%str(msg))
81 return None
82
83 res = {}
84 nameMatch = et.find("Target/name")
85 if nameMatch is None:
86
87 return None
88
89 res["oname"] = nameMatch.text
90 firstResponse = et.find("Target/Resolver")
91 if not firstResponse:
92 return None
93
94 res["otype"] = getattr(firstResponse.find("otype"), "text", None)
95 try:
96 res["RA"] = float(firstResponse.find("jradeg").text)
97 res["dec"] = float(firstResponse.find("jdedeg").text)
98 except (ValueError, AttributeError):
99
100 return None
101 return res
102
104 try:
105 return self.cache.getItem(ident)
106 except KeyError:
107 try:
108 f = urllib.urlopen(self.SVC_URL+urllib.quote(ident))
109 response = f.read()
110 f.close()
111
112 newOb = self._parseXML(response)
113 self.cache.addItem(ident, newOb, save=self.saveNew)
114 return newOb
115 except socket.error:
116 raise base.ui.logOldExc(base.ValidationError(
117 "Simbad is offline, cannot query.",
118 "hscs_pos",
119 hint="If this problem persists, complain to us rather than simbad."))
120
126
129 """returns ra and dec from Simbad for identifier.
130
131 It raises a KeyError if Simbad doesn't know identifier.
132 """
133 return base.caches.getSesame("simbad").getPositionFor(identifier)
134
135
136 base.caches.makeCache("getSesame", lambda key: Sesame(key, saveNew=True))
137
138
139
140
141 from gavo import adql
142
143 @adql.userFunction("gavo_simbadpoint",
144 "(identifier TEXT) -> POINT",
145 """
146 gavo_simbadpoint queries simbad for an identifier and returns the
147 corresponding point. Note that identifier can only be a literal,
148 i.e., as simple string rather than a column name. This is because
149 our database cannot query simbad, and we probably wouldn't want
150 to fire off millions of simbad queries anyway; use simbad's own
151 TAP service for this kind of applications.
152 """,
153 "point", ucd="pos.eq;src")
155 if len(args)!=1 or args[0].type!="characterStringLiteral":
156 raise adql.UfuncError(
157 "gavo_simbadpoint takes exactly one string literal as argument")
158
159 object = args[0].value
160
161 resolver = base.caches.getSesame("web")
162 try:
163 alpha, delta = resolver.getPositionFor(object)
164 except KeyError:
165 raise adql.UfuncError("No simbad position for '%s'"%object)
166
167 return "spoint(RADIANS(%f), RADIANS(%f))"%(alpha, delta)
168
169
170 if __name__=="__main__":
171 s = Sesame(debug=True)
172 print(s.query("M 33"))
173