1 """
2 Services, i.e. combinations of a core and at least one renderer.
3
4 A Service is something that receives some sort of structured data (typically,
5 a nevow context, processes it into input data using a grammar (default is
6 the contextgrammar), pipes it through a core to receive a data set and
7 optionally tinkers with that data set.
8 """
9
10
11
12
13
14
15
16 import os
17 import urllib
18
19 from nevow import inevow
20 from nevow import rend
21
22 from nevow import tags as T, entities as E
23
24 from zope.interface import implements
25
26 from gavo import base
27 from gavo import rsc
28 from gavo import rscdef
29 from gavo import utils
30 from gavo.rsc import table
31 from gavo.rscdef import rmkdef
32 from gavo.svcs import common
33 from gavo.svcs import core
34 from gavo.svcs import inputdef
35 from gavo.svcs import outputdef
36 from gavo.svcs import renderers
37
38
39 MS = base.makeStruct
43 """returns a Data instance created from origTable with columns taken from
44 newColumns.
45
46 The adaptation works like this:
47
48 (1) if names and units of newColumns are a subset of origTable.columns,
49 return a table with the rows from origTable and the columns from
50 newColumns.
51
52 (1a) if origTable has a noPostprocess attribute, proceed to (4)
53
54 (2) if names of newColumns are a subset of origTable.columns match
55 but one or more units don't, set up a conversion routine and create
56 new rows, combining them with newColumns to the result.
57
58 (3) else raise an error.
59
60 (4) Finally, stick the whole thing into a data container.
61
62 This stinks. I'm plotting to do away with it.
63 """
64 if hasattr(origTable, "noPostprocess"):
65 colDiffs = None
66 newTd = origTable.tableDef
67
68 else:
69 colDiffs = base.computeColumnConversions(
70 newColumns, origTable.tableDef.columns)
71 newTd = origTable.tableDef.copy(origTable.tableDef.parent)
72 newTd.columns = newColumns
73
74 if not colDiffs:
75 newTable = table.InMemoryTable(newTd, rows=origTable.rows)
76 newTable.meta_ = origTable.meta_
77 newTable._params = origTable._params
78
79 else:
80 newTd.copyMetaFrom(origTable.tableDef)
81 rmk = rscdef.RowmakerDef(None)
82 for col in newColumns:
83 exprStart = ""
84 if col.name in colDiffs:
85 exprStart = "%s*"%colDiffs[col.name]
86 rmk.feedObject("map", rmkdef.MapRule(rmk, dest=col.name,
87 content_="%svars[%s]"%(exprStart, repr(col.name))
88 ).finishElement(None))
89 newTable = table.InMemoryTable(newTd, validate=False)
90 mapper = rmk.finishElement(None).compileForTableDef(newTd)
91 for r in origTable:
92 newTable.addRow(mapper(r, newTable))
93 newTable._params = origTable._params
94
95 return rsc.wrapTable(newTable, rdSource=origTable.tableDef)
96
107
110 """A nevow.IContainer that has the result and also makes the
111 input dataset accessible.
112
113 It is constructed with an InMemoryTable instance coreResult,
114 a table instance inputTable, the current querymeta, and a service
115 instance.
116
117 If a service is defined, SvcResult adapts coreResult to the
118 columns defined by the service (getCurOutputFields).
119
120 Original currently either is an InMemory table, in which case it gets
121 adapted to what the service expects, or a data.Data instance (or
122 something else; but few renderers will be able to handle "something else"),
123 which is left alone.
124
125 SvcResult also makes queryMeta, inputTable and the service available. This
126 should give renderers access to basically all the information they need. The
127 resultmeta data item collects some of those.
128
129 SvcResult objects must be able to fall back to sensible behaviour
130 without a service. This may be necessary in error handling.
131 """
132 implements(inevow.IContainer)
133
134 - def __init__(self, coreResult, inputTable, queryMeta, service=None):
142
150
153
156
158 """returns the table with role.
159
160 If no such table is available, this will return an empty string.
161 """
162 def _(ctx, data):
163 try:
164 return data.original.getTableWithRole(role)
165 except (AttributeError, base.DataError):
166 return ""
167
168 return _
169
181
182
183 -class Publication(base.Structure, base.ComputedMetaMixin):
184 """A specification of how a service should be published.
185
186 This contains most of the metadata for what is an interface in
187 registry speak.
188 """
189 name_ = "publish"
190
191 _rd = rscdef.RDAttribute()
192 _render = base.UnicodeAttribute("render", default=base.Undefined,
193 description="The renderer the publication will point at.",
194 copyable=True)
195 _sets = base.StringSetAttribute("sets",
196 description="Comma-separated list of sets this service will be"
197 " published in. Predefined are: local=publish on front page,"
198 " ivo_managed=register with the VO registry. If you leave it"
199 " empty, 'local' publication is assumed.",
200 copyable="True")
201 _service = base.ReferenceAttribute("service", default=base.NotGiven,
202 description="Reference for a service actually implementing the"
203 " capability corresponding to this publication. This is"
204 " mainly when there is a vs:WebBrowser service accompanying a VO"
205 " protocol service, and this other service should be published"
206 " in the same resource record. See also the operator's guide.",
207 copyable="True")
208 _auxiliary = base.BooleanAttribute("auxiliary", default=False,
209 description="Auxiliary publications are for capabilities"
210 " not intended to be picked up for all-VO queries, typically"
211 " because they are already registered with other services."
212 " This is mostly used internally; you probably have no reason"
213 " to touch it.")
214
224
231
234
237
240
243
244
245 -class CustomPageFunction(base.Structure, base.RestrictionMixin):
246 """An abstract base for nevow.rend.Page-related functions on services.
247 """
248 _name = base.UnicodeAttribute("name", default=base.Undefined,
249 description="Name of the render function (use this in the"
250 " n:render or n:data attribute in custom templates).",
251 copyable=True, strip=True)
252 _code = base.DataContent(description="Function body of the renderer; the"
253 " arguments are named ctx and data.", copyable=True)
254
256 self._onElementCompleteNext(CustomPageFunction)
257 vars = globals().copy()
258 vars["service"] = self.parent
259 exec ("def %s(ctx, data):\n%s"%(self.name,
260 utils.fixIndentation(self.content_, newIndent=" ",
261 governingLine=1).rstrip())) in vars
262 self.func = vars[self.name]
263
266 """A custom render function for a service.
267
268 Custom render functions can be used to expose certain aspects of a service
269 to Nevow templates. Thus, their definition usually only makes sense with
270 custom templates, though you could, in principle, override built-in
271 render functions.
272
273 In the render functions, you have the names ctx for nevow's context and
274 data for whatever data the template passes to the renderer.
275
276 You can return anything that can be in a stan DOM. Usually, this will be
277 a string. To return HTML, use the stan DOM available under the T namespace.
278
279 As an example, the following code returns the current data as a link::
280
281 return ctx.tag[T.a(href=data)[data]]
282
283 You can access the embedding service as service, the embedding
284 RD as service.rd.
285 """
286 name_ = "customRF"
287
290 """A custom data function for a service.
291
292 Custom data functions can be used to expose certain aspects of a service
293 to Nevow templates. Thus, their definition usually only makes sense with
294 custom templates, though you could, in principle, override built-in
295 render functions.
296
297 In the data functions, you have the names ctx for nevow's context and
298 data for whatever data the template passes to the renderer.
299
300 You can access the embedding service as service, the embedding
301 RD as service.rd.
302
303 You can return arbitrary python objects -- whatever the render functions
304 can deal with. You could, e.g., write::
305
306 <customDF name="now">
307 return datetime.datetime.utcnow()
308 </customDF>
309
310 You also see a nevow context within the function. You can use that to
311 access a query paramter ``order`` like this::
312
313 args = inevow.IRequest(ctx).args
314 sortOrder = args.get("order", ["authors"])
315 """
316 name_ = "customDF"
317
329
333 """returns a list of the service keys defined in //pql#DALIPars.
334
335 This is always the same object, so if you really have to change anything
336 in here, be sure to make copies before touching either the list or
337 the items.
338 """
339 nullSvc = base.parseFromString(Service,
340 """<service><nullCore/><FEED source="//pql#DALIPars"/></service>""")
341
342 res = []
343 for inputKey in nullSvc.serviceKeys:
344
345 inputKey.parent_ = base.NotGiven
346 res.append(inputKey)
347
348 return res
349
350
351 -class Service(base.Structure, base.ComputedMetaMixin,
352 base.StandardMacroMixin, rscdef.IVOMetaMixin):
353 """A service definition.
354
355 A service is a combination of a core and one or more renderers. They
356 can be published, and they carry the metadata published into the VO.
357
358 You can set the defaultSort property on the service to a name of an
359 output column to preselect a sort order. Note again that this will
360 slow down responses for all but the smallest tables unless there is
361 an index on the corresponding column.
362
363 Properties evaluated:
364
365 * defaultSort -- a key to sort on by default with the form renderer.
366 This differs from the dbCore's sortKey in that this does not suppress the
367 widget itself, it just sets a default for its value. Don't use this unless
368 you have to; the combination of sort and limit can have disastrous effects
369 on the run time of queries.
370 * votableRespectsOutputTable -- usually, VOTable output puts in
371 all columns from the underlying database table with low enough
372 verbLevel (essentially). When this property is "True" (case-sensitive),
373 that's not done and only the service's output table is evaluated.
374 """
375 name_ = "service"
376
377 _core = CoreAttribute()
378 _templates = base.DictAttribute("templates", description="Custom"
379 ' nevow templates for this service; use key "form" to replace the Form'
380 " renderer's standard template. Start the path with two slashes to"
381 " access system templates.",
382 itemAttD=rscdef.ResdirRelativeAttribute(
383 "template", description="resdir-relative path to a nevow template"
384 " used for the function given in key."), copyable=True)
385 _publications = base.StructListAttribute("publications",
386 childFactory=Publication, description="Sets and renderers this service"
387 " is published with.")
388 _limitTo = base.UnicodeAttribute("limitTo", default=None,
389 description="Limit access to the group given; the empty default disables"
390 " access control.", copyable="True")
391 _customPage = rscdef.ResdirRelativeAttribute("customPage", default=None,
392 description="resdir-relative path to custom page code. It is used"
393 " by the 'custom' renderer", copyable="True")
394 _allowedRenderers = base.StringSetAttribute("allowed",
395 description="Names of renderers allowed on this service; leave emtpy"
396 " to allow the form renderer only.", copyable=True)
397 _customRF = base.StructListAttribute("customRFs",
398 description="Custom render functions for use in custom templates.",
399 childFactory=CustomRF, copyable=True)
400 _customDF = base.StructListAttribute("customDFs",
401 description="Custom data functions for use in custom templates.",
402 childFactory=CustomDF, copyable=True)
403 _outputTable = base.StructAttribute("outputTable", default=base.NotGiven,
404 childFactory=outputdef.OutputTableDef, copyable=True, description=
405 "The output fields of this service.")
406 _serviceKeys = base.UniquedStructListAttribute("serviceKeys",
407 uniqueAttribute="name", policy="drop",
408 childFactory=inputdef.InputKey, description="Input widgets for"
409 " processing by the service, e.g. output sets.", copyable=True)
410 _defaultRenderer = base.UnicodeAttribute("defaultRenderer",
411 default=None, description="A name of a renderer used when"
412 " none is provided in the URL (lets you have shorter URLs).")
413
414 _rd = rscdef.RDAttribute()
415 _props = base.PropertyAttribute()
416 _original = base.OriginalAttribute()
417
418 metaModel = ("title(1), creationDate(1), description(1),"
419 "subject, referenceURL(1), shortName(!)")
420
421
422
423 htmlLikeFormats = ["HTML", "tar"]
424
425
426
429
479
481 self._onElementCompleteNext(Service)
482
483
484 self.nevowRenderers = {}
485 for customRF in self.customRFs:
486 self.nevowRenderers[customRF.name] = customRF.func
487 self.nevowDataFunctions = {}
488 for customDF in self.customDFs:
489 self.nevowDataFunctions[customDF.name] = customDF.func
490
491 self._compileCustomPage()
492
493 self._computeResourceType()
494
496 if self.customPage:
497 try:
498 modNs, moddesc = utils.loadPythonModule(self.customPage)
499 modNs.RD = self.rd
500 getattr(modNs, "initModule", lambda: None)()
501 page = modNs.MainPage
502 except ImportError:
503 raise base.ui.logOldExc(
504 base.LiteralParseError("customPage", self.customPage,
505 hint="This means that an exception was raised while DaCHS"
506 " tried to import the renderer module. If DaCHS ran"
507 " with --debug, the original traceback is available"
508 " in the logs."))
509 self.customPageCode = page, (os.path.basename(self.customPage),)+moddesc
510
512 """returns the nevow template for the function key on this service.
513 """
514 if key not in self._loadedTemplates:
515 from nevow import loaders
516 tp = self.templates[key]
517 if tp.startswith("//"):
518 self._loadedTemplates[key] = common.loadSystemTemplate(tp[2:])
519 else:
520 self._loadedTemplates[key] = loaders.xmlfile(
521 os.path.join(self.rd.resdir, tp))
522 return self._loadedTemplates[key]
523
525 """returns a user UWS instance for this service.
526
527 This is a service for the UWSAsyncRenderer.
528 """
529 if not hasattr(self, "uws"):
530 from gavo.protocols import useruws
531 self.uws = useruws.makeUWSForService(self)
532 return self.uws
533
534
535
536 @property
538 """is true if there is any ivo_managed publication on this
539 service.
540
541 If renderer is non-None, only publications with this renderer name
542 count.
543 """
544 for pub in self.publications:
545 if "ivo_managed" in pub.sets:
546 if renderer:
547 if pub.render==renderer:
548 return True
549 else:
550 return True
551 return False
552
554 """sets the resType attribute.
555
556 Services are resources, and the registry code wants to know what kind.
557 This method ventures a guess. You can override this decision by setting
558 the resType meta item.
559 """
560 if (self.core.outputTable.columns
561 or "tap" in self.allowed):
562 self.resType = "catalogService"
563 else:
564 self.resType = "nonTabularService"
565
567 """helps _addAutomaticCapabilities.
568
569 Actually, we also use it to generate VOSI capabilties for
570 unpublished services.
571 """
572 vosiSet = set(["ivo_managed"])
573
574
575 if not isinstance(self.core, core.getCore("nullCore")):
576 yield base.makeStruct(Publication,
577 render="availability",
578 sets=vosiSet,
579 parent_=self)
580 yield base.makeStruct(Publication,
581 render="capabilities",
582 sets=vosiSet,
583 parent_=self)
584 yield base.makeStruct(Publication,
585 render="tableMetadata",
586 sets=vosiSet,
587 parent_=self)
588
589
590
591 if isinstance(self.core, core.getCore("dbCore")):
592 if self.core.queriedTable.adql:
593 tapService = base.resolveCrossId("//tap#run")
594 yield base.makeStruct(Publication,
595 render="tap",
596 sets=vosiSet,
597 auxiliary=True,
598 service=tapService,
599 parent_=self)
600
601
602 try:
603 self.getMeta("_example", raiseOnFail=True)
604 yield base.makeStruct(Publication,
605 render="examples",
606 sets=utils.AllEncompassingSet(),
607 parent_=self)
608 except base.NoMetaKey:
609 pass
610
612 """adds some publications that are automatic for certain types
613 of services.
614
615 For services with ivo_managed publications and with useful cores
616 (this keeps out doc-like publications, which shouldn't have VOSI
617 resources), artificial VOSI publications are added.
618
619 If there is _example meta, an examples publication is added.
620
621 If this service exposes a table (i.e., a DbCore with a queriedTable)
622 and that table is adql-readable, also add an auxiliary TAP publication
623 if going to the VO.
624
625 This is being run as an exit function from the parse context as
626 we want the RD to be complete at this point (e.g., _examples
627 meta might come from it). This also lets us liberally resolve
628 references anywhere.
629 """
630 if not self.isVOPublished:
631 return
632
633 for pub in self._iterAutomaticCapabilities():
634 self._publications.feedObject(self, pub)
635
636
637
638
639
640
641
642 if isinstance(self.core, core.getCore("dbCore")):
643 if self.core.queriedTable.adql:
644 tapService = base.resolveCrossId("//tap#run")
645 self.addMeta("servedBy",
646 base.getMetaText(tapService, "title"),
647 ivoId=base.getMetaText(tapService, "identifier"))
648
650 """yields instances for datalink services appropriate for this
651 service.
652
653 The datalink services are taken from the core's queriedTable
654 attribute, if available. As a legacy fallback, for now
655 the datalink property on the service is supported as well for now,
656 but I'll try to get rid of that.
657 """
658 queriedTable = getattr(self.core, "queriedTable", None)
659
660
661 linkGenerated = False
662 if queriedTable:
663 for svcRef in queriedTable.iterMeta(
664 "_associatedDatalinkService.serviceId"):
665 yield base.resolveId(self.rd, str(svcRef))
666 linkGenerated = True
667
668
669
670 if not linkGenerated:
671 svcRef = self.getProperty("datalink", None)
672 if svcRef:
673 yield self.rd.getById(svcRef)
674
676 """returns publications for a set of set names (the names argument).
677
678 In the special case names=None, all allowed renderers are treated
679 as published.
680 """
681 addAllAllowed = False
682 if names is None:
683 names = utils.AllEncompassingSet()
684 addAllAllowed = True
685
686
687
688
689 result = [pub for pub in self.publications
690 if pub.sets & names or names & pub.sets]
691
692
693
694 for dlSvc in self._iterAssociatedDatalinkServices():
695 if "dlget" in dlSvc.allowed:
696 result.append(base.makeStruct(Publication,
697 render="dlget",
698 sets="ivo_managed",
699 service=dlSvc))
700
701 if "dlasync" in dlSvc.allowed:
702 result.append(base.makeStruct(Publication,
703 render="dlasync",
704 sets="ivo_managed",
705 service=dlSvc))
706
707 if "dlmeta" in dlSvc.allowed:
708 result.append(base.makeStruct(Publication,
709 render="dlmeta",
710 sets="ivo_managed",
711 service=dlSvc))
712
713 if addAllAllowed:
714
715
716 alreadyPublished = set(p.render for p in result)
717 for rendName in self.allowed:
718 if not rendName in alreadyPublished:
719 result.append(base.makeStruct(Publication,
720 render=rendName, sets="vosi", service=self))
721
722 for pub in self._iterAutomaticCapabilities():
723 if not pub.render in alreadyPublished:
724 result.append(pub)
725
726 return result
727
728
729 - def getURL(self, rendName, absolute=True, canonical=False, **kwargs):
730 """returns the full canonical access URL of this service together
731 with renderer.
732
733 rendName is the name of the intended renderer in the registry
734 of renderers.
735
736 With absolute, a fully qualified URL is being returned.
737
738 Further keyword arguments are translated into URL parameters in the
739 query part.
740 """
741 basePath = "%s%s/%s"%(base.getConfig("web", "nevowRoot"),
742 self.rd.sourceId, self.id)
743 if absolute:
744 basePath = base.makeAbsoluteURL(basePath, canonical=canonical)
745 res = renderers.getRenderer(rendName
746 ).makeAccessURL(basePath)
747
748 if kwargs:
749 res = res+"?"+urllib.urlencode(kwargs)
750 return res
751
752
753
754 _browserScores = {"form": 10, "external": 12, "fixed": 15,
755 "custom": 3, "img.jpeg": 2, "static": 1}
756
758 """returns a published URL that's suitable for a web browser or None if
759 no such URL can be guessed.
760
761 If you pass fq=False, you will get a path rather than a URL.
762 """
763
764
765
766 browseables = []
767 for rendName in self.allowed:
768 if self.isBrowseableWith(rendName):
769 browseables.append((self._browserScores.get(rendName, -1), rendName))
770 if browseables:
771 return self.getURL(max(browseables)[1], absolute=fq)
772 else:
773 return None
774
776 """returns true if rendering this service through rendName results
777 in something pretty in a web browser.
778 """
779 try:
780 return bool(renderers.getRenderer(rendName).isBrowseable(self))
781 except base.NotFoundError:
782 return False
783
785 """returns a list of table definitions that have something to do with
786 this service.
787
788 This is for VOSI-type requests. Usually, that's just the core's
789 queried table or an output table, except when there is a TAP renderer on
790 the service.
791
792 All this is a bit heuristic; but then again, there's no rigorous
793 definition for what's to be in a tables endpoint either.
794 """
795 tables = []
796
797 qt = getattr(self.core, "queriedTable", None)
798 if qt is None or not qt.columns:
799 qt = self.core.outputTable
800 if qt is None or not qt.columns:
801 if self.outputTable and self.outputTable.columns:
802 qt = self.outputTable
803
804 if qt is not None:
805 tables.append(qt)
806
807
808
809
810
811 if "tap" in self.allowed:
812
813 tables = []
814
815 mth = base.caches.getMTH(None)
816 with base.getTableConn() as conn:
817 for tableName, in conn.query("SELECT table_name"
818 " FROM TAP_SCHEMA.tables"):
819 try:
820 tables.append(mth.getTableDefForTable(tableName))
821 except:
822 base.ui.notifyError("Failure trying to retrieve table definition"
823 " for table %s. Please fix the corresponding RD."%tableName)
824
825 return [t for t in tables if t is not None and t.rd is not None]
826
828 """adds meta to self and data indicating that data is served by
829 service.
830
831 This is used by table/@adql and the publish element on data.
832 """
833 if data.registration:
834 self.addMeta("serviceFor",
835 base.getMetaText(data, "title", default="Anonymous"),
836 ivoId=base.getMetaText(data, "identifier"))
837 data.addMeta("servedBy",
838 base.getMetaText(self, "title"),
839 ivoId=base.getMetaText(self, "identifier"))
840
841
842
843
844 data.rd.addDependency(self.rd, data.rd)
845
846
847
848 _allSet = set(["ALL"])
849
851 """filters columns in columnSource according to verbosity and colum
852 set given in queryMeta.
853
854 Actually, we only evaluate verbosity and requireSet.
855 """
856 if queryMeta["columnSet"]:
857 columnSource = [f for f in columnSource
858 if f.sets==self._allSet or queryMeta["columnSet"]&f.sets]
859
860 verbLevel = queryMeta.get("verbosity", 20)
861 return [f for f in columnSource
862 if f.verbLevel<=verbLevel and not f.hidden]
863
865 """returns a list of OutputFields suitable for a VOTable
866 response described by queryMeta.
867
868 This is the set of all columns in the source table below
869 the verbosity defined in queryMeta, except that columns
870 with a displayHint of noxml present are thrown out, too.
871
872 When the service sets the votableRespectsOutputTable property to
873 "True", the column source is the service's output table rather
874 than the core's one.
875 """
876 if queryMeta.get("verbosity")=="HTML":
877
878 columnSource = self.getHTMLOutputFields(queryMeta)
879
880 elif (self.getProperty("votableRespectsOutputTable", "").lower()=="true"
881 or queryMeta["columnSet"]):
882 columnSource = self.outputTable
883
884 else:
885 columnSource = self.getAllOutputFields()
886
887 fields = self._getFilteredColumns(
888 [f for f in columnSource
889 if f.displayHint.get("noxml")!="true"],
890 queryMeta)
891
892 return rscdef.ColumnList(fields)
893
894
897 """returns a list of OutputFields suitable for an HTML response described
898 by queryMeta.
899
900 This is the service's output table if given, else the core's output
901 table at verbLevel 2. Additional fields can be set by the user.
902
903 raiseOnUnknown is used by customwidgets to avoid exceptions because of
904 bad additional fields during form construction (when they aren't
905 properly caught).
906 """
907 if self.outputTable:
908 columnSource = self.outputTable.columns
909 else:
910 columnSource = self.core.outputTable.columns
911
912 fields = self._getFilteredColumns(columnSource, queryMeta)
913
914
915 if not ignoreAdditionals and queryMeta["additionalFields"]:
916 try:
917 for fieldName in queryMeta["additionalFields"]:
918 col = self.core.outputTable.getColumnByName(fieldName)
919 if isinstance(col, outputdef.OutputField):
920 fields.append(col)
921 else:
922 fields.append(outputdef.OutputField.fromColumn(col))
923 except base.NotFoundError as msg:
924 if raiseOnUnknown:
925 raise base.ValidationError("The additional field %s you requested"
926 " does not exist"%repr(msg.lookedFor), colName="_OUTPUT")
927
928 return rscdef.ColumnList(fields)
929
931 """returns a list of desired output fields for query meta.
932
933 This is for both the core and the formatter to figure out the
934 structure of the tables passed.
935 """
936 queryMeta = queryMeta or common.emptyQueryMeta
937 format = queryMeta.get("format", "HTML")
938 if format in self.htmlLikeFormats:
939 return self.getHTMLOutputFields(queryMeta, raiseOnUnknown=raiseOnUnknown)
940 else:
941 return self._getVOTableOutputFields(queryMeta)
942
944 """Returns a sequence of all available output fields.
945
946 This is what the core gives, and this is what will be declared
947 to the registry. Depending on the output format, the verbosity
948 level and perhaps other user settings, the actuall columns produced
949 will be different.
950 """
951 return self.core.outputTable.columns
952
953
954
983
984 - def getContextGrammarFor(self, renderer, core=None):
985 """returns an ContextGrammar apropriate for this renderer.
986
987 Pass in the core if you already have it as an optimisation (in
988 particular for datalink, where cores aren't automatically cached);
989 if you don't the core will be computed from the renderer.
990
991 In either case, the context grammar simply is built from the core's
992 inputTable.
993 """
994 if isinstance(renderer, basestring):
995 renderer = renderers.getRenderer(renderer)
996 if core is None:
997 core = self.getCoreFor(renderer)
998
999 serviceKeys = list(inputdef.filterInputKeys(self.serviceKeys,
1000 renderer.name, inputdef.getRendererAdaptor(renderer)))
1001
1002 return MS(inputdef.ContextGrammar,
1003 inputTD=core.inputTable,
1004 inputKeys=serviceKeys)
1005
1018
1027
1028 - def run(self, renderer, args, queryMeta=None):
1029 """runs the service, returning an SvcResult.
1030
1031 This is the main entry point for protocol renderers; args is
1032 a dict of lists as provided by request.args.
1033
1034 Pass in queryMeta if convenient or if args is not simply request.args
1035 (but, e.g., nevow formal data). Otherwise, it will be constructed
1036 from args.
1037 """
1038 if isinstance(renderer, basestring):
1039 renderer = renderers.getRenderer(renderer)
1040 if queryMeta is None:
1041 queryMeta = common.QueryMeta.fromNevowArgs(args)
1042
1043 core = self.getCoreFor(renderer)
1044 coreArgs = inputdef.CoreArgs.fromRawArgs(
1045 core.inputTable, args, self.getContextGrammarFor(renderer, core))
1046
1047 return self._runWithInputTable(core, coreArgs, queryMeta)
1048
1049
1050
1051
1055
1057 """returns a list of table names available for TAP querying.
1058
1059 This, really, is an implementation detail for the TAP service and
1060 might go away anytime.
1061 """
1062
1063
1064 from gavo.protocols import tap
1065
1066 schemas = {}
1067 for qname in tap.getAccessibleTables():
1068 try:
1069 schema, name = qname.split(".")
1070 except:
1071 continue
1072 schemas.setdefault(schema, []).append(name)
1073
1074 return ", ".join("%s from the %s schema"%(", ".join(tables), schema)
1075 for schema, tables in schemas.iteritems())
1076
1088
1097