1 """
2 TAP: schema maintenance, job/parameter definition incl. upload and UWS actions.
3 """
4
5
6
7
8
9
10
11 import datetime
12 import os
13
14 from pyparsing import ParseException
15 from twisted.internet import threads
16
17 from gavo import base
18 from gavo import rsc
19 from gavo import svcs
20 from gavo import utils
21 from gavo.protocols import uws
22 from gavo.protocols import uwsactions
23 from gavo.utils import codetricks
24 from gavo.utils import stanxml
25
26
27 RD_ID = "__system__/tap"
28
29
30 EST_TIME_PER_JOB = datetime.timedelta(minutes=10)
31
32
33
34
35 FORMAT_CODES = {
36 base.votableType:
37 ("votable", base.votableType, "VOTable, binary",
38 "ivo://ivoa.net/std/TAPRegExt#output-votable-binary"),
39 "text/xml":
40 ("votable", "text/xml", "VOTable, binary",
41 "ivo://ivoa.net/std/TAPRegExt#output-votable-binary"),
42 "votable":
43 ("votable", base.votableType, "VOTable, binary",
44 "ivo://ivoa.net/std/TAPRegEXT#output-votable-binary"),
45 "application/x-votable+xml;serialization=binary2":
46 ("votableb2", "application/x-votable+xml;serialization=binary2",
47 "VOTable, new binary",
48 "ivo://ivoa.net/std/TAPRegExt#output-votable-binary2"),
49 "votable/b2":
50 ("votableb2", "application/x-votable+xml;serialization=binary2",
51 "VOTable, new binary",
52 "ivo://ivoa.net/std/TAPRegExt#output-votable-binary2"),
53 "vodml":
54 ("vodml", "application/x-votable+xml;version=1.4",
55 "Experimental VOTable 1.4",
56 "http://dc.g-vo.org/output-vodml"),
57 "application/x-votable+xml;serialization=tabledata":
58 ("votabletd", "application/x-votable+xml;serialization=tabledata",
59 "VOTable, tabledata",
60 "ivo://ivoa.net/std/TAPRegEXT#output-votable-td"),
61 "votable/td":
62 ("votabletd", "application/x-votable+xml;serialization=tabledata",
63 "VOTable, tabledata",
64 "ivo://ivoa.net/std/TAPRegEXT#output-votable-td"),
65 "text/csv":
66 ("csv", "text/csv", "CSV without column labels", None),
67 "csv": ("csv_header", "text/csv;header=present",
68 "CSV with column labels", None),
69 "text/csv;header=present":
70 ("csv_header", "text/csv;header=present",
71 "CSV with column labels", None),
72 "text/tab-separated-values":
73 ("tsv", "text/tab-separated-values",
74 "Tab separated values", None),
75 "tsv":
76 ("tsv", "text/tab-separated-values",
77 "Tab separated values", None),
78 "text/plain":
79 ("tsv", "text/plain",
80 "Tab separated values", None),
81 "application/fits":
82 ("fits", "application/fits", "FITS binary table", None),
83 "fits":
84 ("fits", "application/fits", "FITS binary table", None),
85 "text/html":
86 ("html", "text/html", "HTML table", None),
87 "html":
88 ("html", "text/html", "HTML table", None),
89 "json":
90 ("json", "application/json", "JSON", None),
91 "application/json":
92 ("json", "application/json", "JSON", None),
93 "geojson":
94 ("geojson", "application/geo-json", "GeoJSON", None),
95 }
96
97
98 -def _R(**kws): return kws
99
100
101
102 SUPPORTED_LANGUAGES = {
103 "ADQL": _R(versions={
104 "2.0": "ivo://ivoa.net/std/ADQL#v2.0",
105 "2.1": "ivo://ivoa.net/std/ADQL#v2.1"},
106 description="The Astronomical Data Query Language is the standard"
107 " IVOA dialect of SQL; it contains a very general SELECT statement"
108 " as well as some extensions for spherical geometry and higher"
109 " mathematics.")}
110
111
112
113
114 UPLOAD_METHODS = {
115 "upload-inline": "POST inline upload",
116 "upload-http": "http URL",
117 "upload-https": "https URL",
118 "upload-ftp": "ftp URL",
119 }
123 """here for backward compatibility.
124
125 Deprecated.
126 """
127
132 """returns a list of tuples for the supported languages.
133
134 This is tap.SUPPORTED_LANGUAGES in a format suitable for the
135 TAP capabilities element.
136
137 Each tuple returned is made up of
138 (name, description, [(version, ivo-id)...]).
139 """
140 for name, desc in SUPPORTED_LANGUAGES.iteritems():
141 yield (name, desc["description"], desc["versions"].items())
142
161
182
191
194 """returns a list of qualified table names for the TAP-published tables.
195 """
196 with base.getTableConn() as conn:
197 return [r[0]
198 for r in conn.query("select table_name from tap_schema.tables"
199 " order by table_name")]
200
206 """The transition function for TAP jobs.
207
208 There's a hack here: After each transition, when you've released
209 your lock on the job, call checkProcessQueue (in reality, only
210 PhaseAction does this).
211 """
214
216 return "gavo", ["gavo", "--ui", "stingy", "tap", "--", str(wjob.jobId)]
217
218 - def queueJob(self, newState, wjob, ignored):
223
227
231
232 - def killJob(self, newPhase, wjob, ignored):
237
244 from pyparsing import (Word, ZeroOrMore, Suppress, StringEnd,
245 alphas, alphanums, CharsNotIn)
246
247 with utils.pyparsingWhitechars(" \t"):
248 tableName = Word( alphas+"_", alphanums+"_" )
249
250 uri = CharsNotIn(" ;,")
251 uploadSpec = tableName("name") + "," + uri("uri")
252 uploads = uploadSpec + ZeroOrMore(
253 Suppress(";") + uploadSpec) + StringEnd()
254 uploadSpec.addParseAction(lambda s,p,t: (t["name"], t["uri"]))
255 return uploads
256
259 """iterates over pairs of tableName, uploadSource from a TAP upload string.
260 """
261 try:
262 res = utils.pyparseString(getUploadGrammar(), uploadString).asList()
263 return res
264 except ParseException as ex:
265 raise base.ValidationError(
266 "Syntax error in UPLOAD parameter (near %s)"%(ex.loc), "UPLOAD",
267 hint="Note that we only allow regular SQL identifiers as table names,"
268 " i.e., basically only alphanumerics are allowed.")
269
272 @classmethod
273 - def addPar(cls, name, value, job):
275
280
283 """A sentinel class representing a file within a job work directory
284 (as resulting from an upload).
285 """
286 - def __init__(self, jobId, wd, fileName):
289
291
292
293 return self.getURL()
294
296 """returns the URL the file is retrievable under for the life time of
297 the job.
298 """
299 return base.caches.getRD(RD_ID).getById("run").getURL("tap",
300 absolute=True)+"/async/%s/results/%s"%(
301 self.jobId,
302 self.fileName)
303
306
307
308
309
310
311
312
313
314
315
316 _deserialize, _serialize = utils.identity, utils.identity
317
318 @classmethod
319 - def addPar(cls, name, value, job):
331
332 @classmethod
335
336 @classmethod
338
339 return rawName.encode("quoted-printable").replace('/', "=2F")
340
341 @classmethod
353
354
355 -class TAPJob(uws.UWSJobWithWD):
380
381
382
383
384
385 from gavo.utils.stanxml import Element, registerPrefix, schemaURL
386
387 -class Plan(object):
388 """A container for the XML namespace for query plans.
389 """
393
394 - class plan(PlanElement): pass
396 - class query(PlanElement): pass
397 - class min(PlanElement): pass
398 - class max(PlanElement): pass
399 - class value(PlanElement): pass
401 - class rows(PlanElement): pass
402 - class cost(PlanElement): pass
403
404
405 registerPrefix("plan", "http://docs.g-vo.org/std/TAPPlan.xsd",
406 schemaURL("TAPPlan.xsd"))
410 """retrieve a query plan.
411
412 This is actually a TAP action; as we add UWSes, we'll need to think
413 about how we can customize uwsactions my UWS type.
414 """
415 name = "plan"
416
425
427 def recurse(node):
428 (opName, attrs), children = node[:2], node[2:]
429 res = Plan.operation()[
430 Plan.description[opName],
431 Plan.rows[self._formatRange(attrs.get("rows"))],
432 Plan.cost[self._formatRange(attrs.get("cost"))]]
433 for child in children:
434 res[recurse(child)]
435 return res
436 return Plan.plan[
437 Plan.query[query],
438 recurse(planTree)]
439
446
453
454 - def doGET(self, job, request):
458
459
460 -class TAPUWS(uws.UWSWithQueueing):
461 """The UWS responsible for processing async TAP requests.
462 """
463 _baseURLCache = None
464
465 joblistPreamble = ("<?xml-stylesheet href='/static"
466 "/xsl/tap-joblist-to-html.xsl' type='text/xsl'?>")
467 jobdocPreamble = ("<?xml-stylesheet href='/static/xsl/"
468 "tap-job-to-html.xsl' type='text/xsl'?>")
469
474
475 @property
481
483 """returns a fully qualified URL for the job with jobId.
484 """
485 return "%s/%s/%s"%(self.baseURL, "async", jobId)
486
487 WORKER_SYSTEM = TAPUWS()
488
489
490
491
492 -class TAPCore(svcs.Core):
493 """A core for the TAP renderer.
494
495 Right now, this is a no-op and not used by the renderer.
496
497 This will change as we move to regularise the TAP system.
498 """
499 name_ = "tapCore"
500