1 """
2 Common items used by resource definition objects.
3 """
4
5
6
7
8
9
10
11 import datetime
12 import imp
13 import os
14 import re
15 import urllib
16
17 from gavo import base
18 from gavo import utils
19
20
21
22
23
24 _BOOTSTRAPPING = False
28 """an attribute that gives access to the current rd.
29
30 The attribute is always called rd. There is no default, but on
31 the first access, we look for an ancestor with an rd attribute and
32 use that if it exists, otherwise rd will be None. There currently
33 is no way to reset the rd.
34
35 These attributes cannot (yet) be fed, so rd="xxx" won't work.
36 If we need this, the literal would probably be an id.
37 """
38 computed_ = True
39 typeDesc_ = "reference to a resource descriptor"
40
42 base.AttributeDef.__init__(self, "rd", None, "The parent"
43 " resource descriptor; never set this manually, the value will"
44 " be filled in by the software.")
45
62 yield ("rd", property(_getRD))
63
64 def getFullId(self):
65 if self.rd is None:
66 return self.id
67 return "%s#%s"%(self.rd.sourceId, self.id)
68 yield ("getFullId", getFullId)
69
72
75 """is a path that is interpreted relative to the current RD's resdir.
76
77 The parent needs an RDAttribute.
78 """
79 - def __init__(self, name, default=None, description="Undocumented", **kwargs):
83
85 if instance.rd is None:
86
87
88
89
90 return None
91 return instance.rd.resdir
92
95 """An attribute containing a comma separated list of profile names.
96
97 There's the special role name "defaults" for whatever default this
98 profile list was constructed with.
99 """
100 typeDesc_ = "Comma separated list of profile names."
101
102 - def __init__(self, name, default, description):
105
106 @property
109
111 pNames = set()
112 for pName in value.split(","):
113 pName = pName.strip()
114 if not pName:
115 continue
116 if pName=="defaults":
117 pNames = pNames|self.default_
118 else:
119 pNames.add(pName)
120 return pNames
121
123
124
125 return ", ".join(value)
126
129 """A mixin for structures declaring access to database objects (tables,
130 schemas).
131
132 Access is managed on the level of database profiles. Thus, the names
133 here are not directly role names in the database.
134
135 We have two types of privileges: "All" means at least read and write,
136 and "Read" meaning at least read and lookup.
137 """
138 _readProfiles = ProfileListAttribute("readProfiles",
139 default=base.getConfig("db", "queryProfiles"),
140 description="A (comma separated) list of profile names through"
141 " which the object can be read.")
142 _allProfiles = ProfileListAttribute("allProfiles",
143 default=base.getConfig("db", "maintainers"),
144 description="A (comma separated) list of profile names through"
145 " which the object can be written or administred (oh, and the"
146 " default is not admin, msdemlei but is the value of [db]maintainers)")
147
230
233 """A request for registration of a data or table item.
234
235 This is much like publish for services, just for data and tables;
236 since they have no renderers, you can only have one register element
237 per such element.
238
239 Data registrations may refer to published services that make their
240 data available.
241 """
242 name_ = "publish"
243 docName_ = "publish (data)"
244 aliases = ["register"]
245
246 _sets = base.StringSetAttribute("sets", default=frozenset(["ivo_managed"]),
247 description="A comma-separated list of sets this data will be"
248 " published in. To publish data to the VO registry, just"
249 " say ivo_managed here. Other sets probably don't make much"
250 " sense right now. ivo_managed also is the default.")
251
252 _servedThrough = base.ReferenceListAttribute("services",
253 description="A DC-internal reference to a service that lets users"
254 " query that within the data collection; tables with adql=True"
255 " are automatically declared to be servedBy the TAP service.")
256
257
258
259
260 auxiliary = True
261
263 """returns true if at least one table published is available for
264 TAP/ADQL.
265 """
266 if getattr(self.parent, "adql", False):
267
268 return True
269
270 for t in getattr(self.parent, "iterTableDefs", lambda: [])():
271
272 if t.adql:
273 return True
274
275 return False
276
278 """adds servedBy and serviceFrom metadata to data, service pairs
279 in this registration.
280 """
281 if self.publishedForADQL():
282 tapSvc = base.caches.getRD("//tap").getById("run")
283 if not tapSvc in self.services:
284 self.services.append(tapSvc)
285
286 for srv in self.services:
287 srv.declareServes(self.parent)
288
291 """A list of column.Columns (or derived classes) that takes
292 care that no duplicates (in name) occur.
293
294 If you add a field with the same dest to a ColumnList, the previous
295 instance will be overwritten. The idea is that you can override
296 ColumnList in, e.g., interfaces later on.
297
298 Also, two ColumnLists are considered equal if they contain the
299 same names.
300
301 After construction, you should set the withinId attribute to
302 something that will help make sense of error messages.
303 """
305 list.__init__(self, *args)
306 self.nameIndex = dict([(c.name, ct) for ct, c in enumerate(self)])
307 self.withinId = "unnamed table"
308
310 return fieldName in self.nameIndex
311
313 if isinstance(other, ColumnList):
314 myFields = set([f.name for f in self
315 if f.name not in self.internallyUsedFields])
316 otherFields = set([f.name for f in other
317 if f.name not in self.internallyUsedFields])
318 return myFields==otherFields
319 elif other==[] and len(self)==0:
320 return True
321 return False
322
324 """returns a deep copy of self.
325
326 This means that all child structures are being copied. In that
327 process, they receive a new parent, which is why you need to
328 pass one in.
329 """
330 return self.__class__([c.copy(newParent) for c in self])
331
333 try:
334 return self.__idIndex
335 except AttributeError:
336 self.__idIndex = dict((c.id, c) for c in self if c.id is not None)
337 return self.__idIndex
338
340 """adds the Column item to the data field list.
341
342 It will overwrite a Column of the same name if such a thing is already
343 in the list. Indices are updated.
344 """
345 key = item.name
346 if key in self.nameIndex:
347 nameInd = self.nameIndex[key]
348 assert self[nameInd].name==key, \
349 "Someone tampered with ColumnList"
350 self[nameInd] = item
351 else:
352 self.nameIndex[item.name] = len(self)
353 list.append(self, item)
354
355 - def replace(self, oldCol, newCol):
356 ind = 0
357 while True:
358 if self[ind]==oldCol:
359 self[ind] = newCol
360 break
361 ind += 1
362 del self.nameIndex[oldCol.name]
363 self.nameIndex[newCol.name] = ind
364
368
370 for item in seq:
371 self.append(item)
372
374 """returns the column with name.
375
376 It will raise a NotFoundError if no such column exists.
377 """
378 try:
379 return self[self.nameIndex[name]]
380 except KeyError:
381 try:
382 return self[self.nameIndex[utils.QuotedName(name)]]
383 except KeyError:
384 raise base.NotFoundError(name, what="column", within=self.withinId)
385
387 """returns the column with id.
388
389 It will raise a NotFoundError if no such column exists.
390 """
391 try:
392 return self.getIdIndex()[id]
393 except KeyError:
394 raise base.NotFoundError(id, what="column", within=self.withinId)
395
397 """returns the column having utype.
398
399 This should be unique, but this method does not check for uniqueness.
400 """
401 utype = utype.lower()
402 for item in self:
403 if item.utype and item.utype.lower()==utype:
404 return item
405 raise base.NotFoundError(utype, what="column with utype",
406 within=self.withinId)
407
409 """returns all columns having ucd.
410 """
411 return [item for item in self if item.ucd==ucd]
412
414 """retuns the single, unique column having ucd.
415
416 It raises a ValueError if there is no such column or more than one.
417 """
418 cols = self.getColumnsByUCD(ucd)
419 if len(cols)==1:
420 return cols[0]
421 elif cols:
422 raise ValueError("More than one column for %s"%ucd)
423 else:
424 raise ValueError("No column for %s"%ucd)
425
427 """returns the single, unique column having one of ucds.
428
429 This method has a confusing interface. It sole function is to
430 help when there are multiple possible UCDs that may be interesting
431 (like pos.eq.ra;meta.main and POS_EQ_RA_MAIN). It should only be
432 used for such cases.
433 """
434 for ucd in ucds:
435 try:
436 return self.getColumnByUCD(ucd)
437 except ValueError:
438 pass
439 raise ValueError("No unique column for any of %s"%", ".join(ucds))
440
443 """An adapter from a ColumnList to a structure attribute.
444 """
445 @property
448
449 - def getCopy(self, instance, newParent, ctx):
452
453 - def replace(self, instance, oldStruct, newStruct):
454 if oldStruct.name!=newStruct.name:
455 raise base.StructureError("Can only replace fields of the same"
456 " name in a ColumnList")
457 getattr(instance, self.name_).append(newStruct)
458
461 """defines an attribute NamePath used for resolution of "original"
462 attributes.
463
464 The NamePathAttribute provides a resolveName method as expected
465 by base.OriginalAttribute.
466 """
467 typeDesc_ = "id reference"
468
470 if "description" not in kwargs:
471 kwargs["description"] = ("Reference to an element tried to"
472 " satisfy requests for names in id references of this"
473 " element's children.")
474 base.AtomicAttribute.__init__(self, name="namePath", **kwargs)
475
477 def resolveName(instance, context, id):
478 if hasattr(instance, "parentTable"):
479 try:
480 return base.resolveNameBased(instance.parentTable, id)
481 except base.NotFoundError:
482
483 pass
484
485 if hasattr(instance, "getByName"):
486 try:
487 return instance.getByName(id)
488 except base.NotFoundError:
489 pass
490
491 np = instance.namePath
492 if np is None and instance.parent:
493 np = getattr(instance.parent, "namePath", None)
494 if np is None:
495 raise base.NotFoundError(id, "Element with name", repr(self),
496 hint="No namePath here")
497 res = context.resolveId(np+"."+id)
498 return res
499 yield "resolveName", resolveName
500
503
506
507
508 _atPattern = re.compile("@(%s)"%utils.identifierPattern.pattern[:-1])
511 """replaces @<identifier> with <dictName>["<identifier>"] in src.
512
513 We do this to support this shortcut in the vicinity of rowmakers (i.e.,
514 there and in procApps).
515 """
516 return _atPattern.sub(r'%s["\1"]'%dictName, src)
517
523 """returns the element for the DaCHS reference ``refString``.
524
525 ``refString`` has the form ``rdId[#subRef]``; ``rdId`` can be
526 filesystem-relative, but the RD referenced must be below ``inputsDir``
527 anyway.
528
529 You can pass a structure class into ``forceType``, and a ``StructureError``
530 will be raised if what's pointed to by the id isn't of that type.
531
532 You should usually use ``base.resolveCrossId`` instead of this from *within*
533 DaCHS. This is intended for code handling RD ids from users.
534
535 This supports further keyword arguments to getRD.
536 """
537
538 try:
539 cwdInInputs = utils.getRelativePath(os.getcwd(),
540 base.getConfig("inputsDir"), liberalChars=True)
541 except ValueError:
542
543 cwdInInputs = None
544
545 try:
546 return base.resolveCrossId(refString, forceType=forceType, **kwargs)
547 except base.RDNotFound:
548 if cwdInInputs:
549 return base.resolveCrossId("%s/%s"%(cwdInInputs, refString),
550 forceType=forceType)
551 raise
552
556 """returns the standard DaCHS PubDID for ``path``.
557
558 The publisher dataset identifier (PubDID) is important in protocols like
559 SSAP and obscore. If you use this function, the PubDID will be your
560 authority, the path compontent ~, and the inputs-relative path of
561 the input file as the parameter.
562
563 ``path`` can be relative, in which case it is interpreted relative to
564 the DaCHS ``inputsDir.``
565
566 You *can* define your PubDIDs in a different way, but you'd then need
567 to provide a custom descriptorGenerator to datalink services (and
568 might need other tricks). If your data comes from plain files, use
569 this function.
570
571 In a rowmaker, you'll usually use the \\standardPubDID macro.
572 """
573
574
575
576 if path[0]!="/":
577 path = os.path.join(base.getConfig("inputsDir"), path)
578
579 return "ivo://%s/~?%s"%(
580 base.getConfig("ivoa", "authority"),
581 getInputsRelativePath(path, liberalChars=True))
582
599
605 """returns an accref from a standard DaCHS PubDID.
606
607 This is basically the inverse of getStandardPubDID. It will raise
608 NotFound if pubdid "looks like a URI" (implementation detail: has a colon
609 in the first 10 characters) and does not start with ivo://<authority>/~?.
610 If it's not a URI, we assume it's a local accref and just return it.
611
612 The function does not check if the remaining characters are a valid
613 accref, much less whether it can be resolved.
614
615 authBase's default will reflect you system's settings on your installation,
616 which probably is not what's given in this documentation.
617 """
618 if ":" not in pubdid[:10]:
619 return pubdid
620
621 if not pubdid.startswith(authBase):
622 raise base.NotFoundError(pubdid,
623 "The authority in the dataset identifier",
624 "the authorities managed here")
625 return pubdid[len(authBase):]
626
640