1 from xml.dom import minidom
2 import weakref
3 import re
4
5 from intermine.util import openAnything, ReadableException
6
7 """
8 Classes representing the data model
9 ===================================
10
11 Representations of tables and columns, and behaviour
12 for validating connections between them.
13
14 """
15
16 __author__ = "Alex Kalderimis"
17 __organization__ = "InterMine"
18 __license__ = "LGPL"
19 __contact__ = "dev@intermine.org"
22 """
23 A class representing columns on database tables
24 ===============================================
25
26 The base class for attributes, references and collections. All
27 columns in DB tables are represented by fields
28
29 SYNOPSIS
30 --------
31
32 >>> service = Service("http://www.flymine.org/query/service")
33 >>> model = service.model
34 >>> cd = model.get_class("Gene")
35 >>> print "Gene has", len(cd.fields), "fields"
36 >>> for field in gene_cd.fields:
37 ... print " - ", field
38 Gene has 45 fields
39 - CDSs is a group of CDS objects, which link back to this as gene
40 - GLEANRsymbol is a String
41 - UTRs is a group of UTR objects, which link back to this as gene
42 - alleles is a group of Allele objects, which link back to this as gene
43 - chromosome is a Chromosome
44 - chromosomeLocation is a Location
45 - clones is a group of CDNAClone objects, which link back to this as gene
46 - crossReferences is a group of CrossReference objects, which link back to this as subject
47 - cytoLocation is a String
48 - dataSets is a group of DataSet objects, which link back to this as bioEntities
49 - downstreamIntergenicRegion is a IntergenicRegion
50 - exons is a group of Exon objects, which link back to this as gene
51 - flankingRegions is a group of GeneFlankingRegion objects, which link back to this as gene
52 - goAnnotation is a group of GOAnnotation objects
53 - homologues is a group of Homologue objects, which link back to this as gene
54 - id is a Integer
55 - interactions is a group of Interaction objects, which link back to this as gene
56 - length is a Integer
57 ...
58
59 @see: L{Attribute}
60 @see: L{Reference}
61 @see: L{Collection}
62 """
63 - def __init__(self, name, type_name, class_origin):
64 """
65 Constructor - DO NOT USE
66 ========================
67
68 THIS CLASS IS NOT MEANT TO BE INSTANTIATED DIRECTLY
69
70 you are unlikely to need to do
71 so anyway: it is recommended you access fields
72 through the classes generated by the model
73
74 @param name: The name of the reference
75 @param type_name: The name of the model.Class this refers to
76 @param class_origin: The model.Class this was declared in
77
78 """
79 self.name = name
80 self.type_name = type_name
81 self.type_class = None
82 self.declared_in = class_origin
83
85 return self.name + " is a " + self.type_name
86
89
90 @property
92 raise Exception("Fields should never be directly instantiated")
93
95 """
96 Attributes represent columns that contain actual data
97 =====================================================
98
99 The Attribute class inherits all the behaviour of L{intermine.model.Field}
100 """
101
102 @property
105
107 """
108 References represent columns that refer to records in other tables
109 ==================================================================
110
111 In addition the the behaviour and properties of Field, references
112 may also have a reverse reference, if the other record points
113 back to this one as well. And all references will have their
114 type upgraded to a type_class during parsing
115 """
116 - def __init__(self, name, type_name, class_origin, reverse_ref=None):
117 """
118 Constructor
119 ===========
120
121 In addition to the a parameters of Field, Reference also
122 takes an optional reverse reference name (str)
123
124 @param name: The name of the reference
125 @param type_name: The name of the model.Class this refers to
126 @param class_origin: The model.Class this was declared in
127 @param reverse_ref: The name of the reverse reference (default: None)
128
129 """
130 self.reverse_reference_name = reverse_ref
131 super(Reference, self).__init__(name, type_name, class_origin)
132 self.reverse_reference = None
134 """
135 Return a string representation
136 ==============================
137
138 @rtype: str
139 """
140 s = super(Reference, self).__repr__()
141 if self.reverse_reference is None:
142 return s
143 else:
144 return s + ", which links back to this as " + self.reverse_reference.name
145
146 @property
149
151 """
152 Collections are references which refer to groups of objects
153 ===========================================================
154
155 Collections have all the same behaviour and properties as References
156 """
158 """Return a string representation"""
159 ret = super(Collection, self).__repr__().replace(" is a ", " is a group of ")
160 if self.reverse_reference is None:
161 return ret + " objects"
162 else:
163 return ret.replace(", which links", " objects, which link")
164
165 @property
168
170 """
171 An abstraction of database tables in the data model
172 ===================================================
173
174 These objects refer to the table objects in the
175 InterMine ORM layer.
176
177 SYNOPSIS
178 --------
179
180 >>> service = Service("http://www.flymine.org/query/service")
181 >>> model = service.model
182 >>>
183 >>> if "Gene" in model.classes:
184 ... gene_cd = model.get_class("Gene")
185 ... print "Gene has", len(gene_cd.fields), "fields"
186 ... for field in gene_cd.fields:
187 ... print " - ", field.name
188
189 OVERVIEW
190 --------
191
192 Each class can have attributes (columns) of various types,
193 and can have references to other classes (tables), on either
194 a one-to-one (references) or one-to-many (collections) basis
195
196 Classes should not be instantiated by hand, but rather used
197 as part of the model they belong to.
198
199 """
200
201
202 - def __init__(self, name, parents, model, interface = True):
203 """
204 Constructor - Creates a new Class descriptor
205 ============================================
206
207 >>> cd = intermine.model.Class("Gene", ["SequenceFeature"])
208 <intermine.model.Class: Gene>
209
210 This constructor is called when deserialising the
211 model - you should have no need to create Classes by hand
212
213 @param name: The name of this class
214 @param parents: a list of parental names
215
216 """
217 self.name = name
218 self.parents = parents
219 self.model = model
220 self.parent_classes = []
221 self.is_interface = interface
222 self.field_dict = {}
223 self.has_id = "Object" not in parents
224 if self.has_id:
225
226 id_field = Attribute("id", "Integer", self)
227 self.field_dict["id"] = id_field
228
230 return "<%s.%s %s.%s>" % (self.__module__, self.__class__.__name__,
231 self.model.package_name if hasattr(self.model, 'package_name') else "__test__", self.name)
232
233 @property
235 """
236 The fields of this class
237 ========================
238
239 The fields are returned sorted by name. Fields
240 includes all Attributes, References and Collections
241
242 @rtype: list(L{Field})
243 """
244 return sorted(self.field_dict.values(), key=lambda field: field.name)
245
247 for f in self.field_dict.values():
248 yield f
249
251 if isinstance(item, Field):
252 return item in self.field_dict.values()
253 else:
254 return str(item) in self.field_dict
255
256 @property
258 """
259 The fields of this class which contain data
260 ===========================================
261
262 @rtype: list(L{Attribute})
263 """
264 return filter(lambda x: isinstance(x, Attribute), self.fields)
265
266 @property
268 """
269 fields which reference other objects
270 ====================================
271
272 @rtype: list(L{Reference})
273 """
274 def isRef(x): return isinstance(x, Reference) and not isinstance(x, Collection)
275 return filter(isRef, self.fields)
276
277 @property
279 """
280 fields which reference many other objects
281 =========================================
282
283 @rtype: list(L{Collection})
284 """
285 return filter(lambda x: isinstance(x, Collection), self.fields)
286
288 """
289 Get a field by name
290 ===================
291
292 The standard way of retrieving a field
293
294 @raise ModelError: if the Class does not have such a field
295
296 @rtype: subclass of L{intermine.model.Field}
297 """
298 if name in self.field_dict:
299 return self.field_dict[name]
300 else:
301 raise ModelError("There is no field called %s in %s" % (name, self.name))
302
303 - def isa(self, other):
304 """
305 Check if self is, or inherits from other
306 ========================================
307
308 This method validates statements about inheritance.
309 Returns true if the "other" is, or is within the
310 ancestry of, this class
311
312 Other can be passed as a name (str), or as the class object itself
313
314 @rtype: boolean
315 """
316 if isinstance(other, Class):
317 other_name = other.name
318 else:
319 other_name = other
320 if self.name == other_name:
321 return True
322 if other_name in self.parents:
323 return True
324 for p in self.parent_classes:
325 if p.isa(other):
326 return True
327 return False
328
329
330 -class Path(object):
331 """
332 A class representing a validated dotted string path
333 ===================================================
334
335 A path represents a connection between records and fields
336
337 SYNOPSIS
338 --------
339
340 >>> service = Service("http://www.flymine.org/query/service")
341 model = service.model
342 path = model.make_path("Gene.organism.name")
343 path.is_attribute()
344 ... True
345 >>> path2 = model.make_path("Gene.proteins")
346 path2.is_attribute()
347 ... False
348 >>> path2.is_reference()
349 ... True
350 >>> path2.get_class()
351 ... <intermine.model.Class: gene>
352
353 OVERVIEW
354 --------
355
356 This class is used for performing validation on dotted path strings.
357 The simple act of parsing it into existence will validate the path
358 to some extent, but there are additional methods for verifying certain
359 relationships as well
360 """
361 - def __init__(self, path, model, subclasses={}):
362 """
363 Constructor
364 ===========
365
366 >>> path = Path("Gene.name", model)
367
368 You will not need to use this constructor directly. Instead,
369 use the "make_path" method on the model to construct paths for you.
370
371 @param path: the dotted path string (eg: Gene.proteins.name)
372 @type path: str
373 @param model: the model to validate the path against
374 @type model: L{Model}
375 @param subclasses: a dict which maps subclasses (defaults to an empty dict)
376 @type subclasses: dict
377 """
378 self.model = weakref.proxy(model)
379 self.subclasses = subclasses
380 if isinstance(path, Class):
381 self._string = path.name
382 self.parts = [path]
383 else:
384 self._string = str(path)
385 self.parts = model.parse_path_string(str(path), subclasses)
386
389
391 return '<' + self.__module__ + "." + self.__class__.__name__ + ": " + self._string + '>'
392
394 """
395 The path one step above this path.
396 ==================================
397
398 >>> p1 = Path("Gene.exons.name", model)
399 >>> p2 = p1.prefix()
400 >>> print p2
401 ... Gene.exons
402
403 """
404 parts = list(self.parts)
405 parts.pop()
406 if len(parts) < 1:
407 raise PathParseError(str(self) + " does not have a prefix")
408 s = ".".join(map(lambda x: x.name, parts))
409 return Path(s, self.model._unproxied(), self.subclasses)
410
412 """
413 Construct a new path by adding elements to the end of this one.
414 ===============================================================
415
416 >>> p1 = Path("Gene.exons", model)
417 >>> p2 = p1.append("name")
418 >>> print p2
419 ... Gene.exons.name
420
421 This is the inverse of prefix.
422 """
423 s = str(self) + "." + ".".join(elements)
424 return Path(s, self.model._unproxied(), self.subclasses)
425
426 @property
428 """
429 The descriptor for the first part of the string. This should always a class descriptor.
430
431 @rtype: L{intermine.model.Class}
432 """
433 return self.parts[0]
434
435 @property
437 """
438 The descriptor for the last part of the string.
439
440 @rtype: L{model.Class} or L{model.Field}
441 """
442 return self.parts[-1]
443
445 """
446 Return the class object for this path, if it refers to a class
447 or a reference. Attribute paths return None
448
449 @rtype: L{model.Class}
450 """
451 if self.is_class():
452 return self.end
453 elif self.is_reference():
454 if str(self) in self.subclasses:
455 return self.model.get_class(self.subclasses[str(self)])
456 return self.end.type_class
457 else:
458 return None
459
460 end_class = property(get_class)
461
463 """
464 Return true if the path is a reference, eg: Gene.organism or Gene.proteins
465 Note: Collections are ALSO references
466
467 @rtype: boolean
468 """
469 return isinstance(self.end, Reference)
470
472 """
473 Return true if the path just refers to a class, eg: Gene
474
475 @rtype: boolean
476 """
477 return isinstance(self.end, Class)
478
480 """
481 Return true if the path refers to an attribute, eg: Gene.length
482
483 @rtype: boolean
484 """
485 return isinstance(self.end, Attribute)
486
488 return str(self) == str(other)
489
491 i = hash(str(self))
492 return reduce(lambda a, b: a ^ b, [hash(k) ^ hash(v) for k, v in self.subclasses.items()], i)
493
495
497 self.op = op
498 self.left = left
499 self.right = right
500
503
506
508 for n in [self.left, self.right]:
509 for subn in n:
510 yield subn
511
512 - def as_logic(self, codes = None, start = 'A'):
513 if codes is None:
514 codes = (chr(c) for c in range(ord(start), ord('Z')))
515 return "(%s %s %s)" % (self.left.as_logic(codes), self.op, self.right.as_logic(codes))
516
518
520 self.vargs = args
521 self.kwargs = kwargs
522
525
526 - def as_logic(self, codes = None, start = 'A'):
527 if codes is None:
528 codes = (chr(c) for c in range(ord(start), ord('Z')))
529 return codes.next()
530
532
533 - def as_logic(self, code = None, start = 'A'):
535
537 """
538 A representation of a path in a query that can be constrained
539 =============================================================
540
541 Column objects allow constraints to be constructed in something
542 close to a declarative style
543 """
544
545 - def __init__(self, path, model, subclasses={}, query=None, parent = None):
546 self._model = model
547 self._query = query
548 self._subclasses = subclasses
549 self._parent = parent
550 self.filter = self.where
551 if isinstance(path, Path):
552 self._path = path
553 else:
554 self._path = model.make_path(path, subclasses)
555 self._branches = {}
556
558 """
559 Create a new query with this column as the base class, selecting the given fields.
560
561 If no fields are given, then just this column will be selected.
562 """
563 q = self._model.service.new_query(str(self))
564 if len(cols):
565 q.select(*cols)
566 else:
567 q.select(self)
568 return q
569
570 - def where(self, *args, **kwargs):
571 """
572 Create a new query based on this column, filtered with the given constraint.
573
574 also available as "filter"
575 """
576 q = self.select()
577 return q.where(*args, **kwargs)
578
580 """
581 Return the number of values in this column.
582 """
583 return self.select().count()
584
586 """
587 Iterate over the things this column represents.
588
589 In the case of an attribute column, that is the values it may have. In the case
590 of a reference or class column, it is the objects that this path may refer to.
591 """
592 q = self.select()
593 if self._path.is_attribute():
594 for row in q.rows():
595 yield row[0]
596 else:
597 for obj in q:
598 yield obj
599
601 if name in self._branches:
602 return self._branches[name]
603 cld = self._path.get_class()
604 if cld is not None:
605 try:
606 fld = cld.get_field(name)
607 branch = Column(str(self) + "." + name, self._model, self._subclasses, self._query, self)
608 self._branches[name] = branch
609 return branch
610 except ModelError, e:
611 raise AttributeError(str(e))
612 raise AttributeError("No attribute '" + name + "'")
613
615 return str(self._path)
616
618 if isinstance(other, tuple):
619 return ConstraintNode(str(self), 'LOOKUP', *other)
620 else:
621 return ConstraintNode(str(self), 'LOOKUP', str(other))
622
625
626 __lshift__ = __rshift__
627
629 if other is None:
630 return ConstraintNode(str(self), "IS NULL")
631 elif isinstance(other, Column):
632 return ConstraintNode(str(self), "IS", str(other))
633 elif hasattr(other, "make_list_constraint"):
634 return other.make_list_constraint(str(self), "IN")
635 elif isinstance(other, list):
636 return ConstraintNode(str(self), "ONE OF", other)
637 else:
638 return ConstraintNode(str(self), "=", other)
639
641 if other is None:
642 return ConstraintNode(str(self), "IS NOT NULL")
643 elif isinstance(other, Column):
644 return ConstraintNode(str(self), "IS NOT", str(other))
645 elif hasattr(other, "make_list_constraint"):
646 return other.make_list_constraint(str(self), "NOT IN")
647 elif isinstance(other, list):
648 return ConstraintNode(str(self), "NONE OF", other)
649 else:
650 return ConstraintNode(str(self), "!=", other)
651
653 if hasattr(other, "make_list_constraint"):
654 return other.make_list_constraint(str(self), "NOT IN")
655 elif isinstance(other, list):
656 return ConstraintNode(str(self), "NONE OF", other)
657 raise TypeError("Invalid argument for xor: %r" % other)
658
659 - def in_(self, other):
660 if hasattr(other, "make_list_constraint"):
661 return other.make_list_constraint(str(self), "IN")
662 elif isinstance(other, list):
663 return ConstraintNode(str(self), "ONE OF", other)
664 raise TypeError("Invalid argument for in_: %r" % other)
665
667 if isinstance(other, Column):
668 self._parent._subclasses[str(self)] = str(other)
669 self._parent._branches = {}
670 return CodelessNode(str(self), str(other))
671 try:
672 return self.in_(other)
673 except TypeError:
674 return ConstraintNode(str(self), "<", other)
675
677 if isinstance(other, Column):
678 return CodelessNode(str(self), str(other))
679 try:
680 return self.in_(other)
681 except TypeError:
682 return ConstraintNode(str(self), "<=", other)
683
686
689
691 """
692 A class for representing the data model of an InterMine datawarehouse
693 =====================================================================
694
695 An abstraction of the database schema
696
697 SYNOPSIS
698 --------
699
700 >>> service = Service("http://www.flymine.org/query/service")
701 >>> model = service.model
702 >>> model.get_class("Gene")
703 <intermine.model.Class: Gene>
704
705 OVERVIEW
706 --------
707
708 This class represents the data model - ie. an abstraction
709 of the database schema. It can be used to introspect what
710 data is available and how it is inter-related
711 """
712
713 NUMERIC_TYPES = frozenset(["int", "Integer", "float", "Float", "double", "Double", "long", "Long", "short", "Short"])
714
715 - def __init__(self, source, service=None):
716 """
717 Constructor
718 ===========
719
720 >>> model = Model(xml)
721
722 You will most like not need to create a model directly,
723 instead get one from the Service object:
724
725 @see: L{intermine.webservice.Service}
726
727 @param source: the model.xml, as a local file, string, or url
728 """
729 assert source is not None
730 self.source = source
731 if service is not None:
732 self.service = weakref.proxy(service)
733 else:
734 self.service = service
735 self.classes= {}
736 self.parse_model(source)
737 self.vivify()
738
739
740 self.table = self.column
741
743 """
744 Create classes, attributes, references and collections from the model.xml
745 =========================================================================
746
747 The xml can be provided as a file, url or string. This method
748 is called during instantiation - it does not need to be called
749 directly.
750
751 @param source: the model.xml, as a local file, string, or url
752 @raise ModelParseError: if there is a problem parsing the source
753 """
754 try:
755 io = openAnything(source)
756 src = ''.join(io.readlines())
757 doc = minidom.parseString(src)
758 for node in doc.getElementsByTagName('model'):
759 self.name = node.getAttribute('name')
760 self.package_name = node.getAttribute('package')
761 assert node.nextSibling is None, "More than one model element"
762 assert self.name and self.package_name, "No model name or package name"
763
764 for c in doc.getElementsByTagName('class'):
765 class_name = c.getAttribute('name')
766 assert class_name, "Name not defined in" + c.toxml()
767 def strip_java_prefix(x):
768 return re.sub(r'.*\.', '', x)
769 parents = map(strip_java_prefix,
770 c.getAttribute('extends').split(' '))
771 interface = c.getAttribute('is-interface') == 'true'
772 cl = Class(class_name, parents, self, interface)
773 for a in c.getElementsByTagName('attribute'):
774 name = a.getAttribute('name')
775 type_name = strip_java_prefix(a.getAttribute('type'))
776 at = Attribute(name, type_name, cl)
777 cl.field_dict[name] = at
778 for r in c.getElementsByTagName('reference'):
779 name = r.getAttribute('name')
780 type_name = r.getAttribute('referenced-type')
781 linked_field_name = r.getAttribute('reverse-reference')
782 ref = Reference(name, type_name, cl, linked_field_name)
783 cl.field_dict[name] = ref
784 for co in c.getElementsByTagName('collection'):
785 name = co.getAttribute('name')
786 type_name = co.getAttribute('referenced-type')
787 linked_field_name = co.getAttribute('reverse-reference')
788 col = Collection(name, type_name, cl, linked_field_name)
789 cl.field_dict[name] = col
790 self.classes[class_name] = cl
791 except Exception, error:
792 model_src = src if src is not None else source
793 raise ModelParseError("Error parsing model", model_src, error)
794 finally:
795 if io is not None:
796 io.close()
797
799 """
800 Make names point to instances and insert inherited fields
801 =========================================================
802
803 This method ensures the model is internally consistent. This method
804 is called during instantiaton. It does not need to be called
805 directly.
806
807 @raise ModelError: if the names point to non-existent objects
808 """
809 for c in self.classes.values():
810 c.parent_classes = self.to_ancestry(c)
811 for pc in c.parent_classes:
812 c.field_dict.update(pc.field_dict)
813 for f in c.fields:
814 f.type_class = self.classes.get(f.type_name)
815 if hasattr(f, 'reverse_reference_name') and f.reverse_reference_name != '':
816 rrn = f.reverse_reference_name
817 f.reverse_reference = f.type_class.field_dict[rrn]
818
820 """
821 Returns the lineage of the class
822 ================================
823
824 >>> classes = Model.to_ancestry(cd)
825
826 Returns the class' parents, and all the class' parents' parents
827
828 @rtype: list(L{intermine.model.Class})
829 """
830 parents = cd.parents
831 def defined(x): return x is not None
832 def to_class(x): return self.classes.get(x)
833 ancestry = filter(defined, map(to_class, parents))
834 for ancestor in ancestry:
835 ancestry.extend(self.to_ancestry(ancestor))
836 return ancestry
837
839 """
840 take a list of class names and return a list of classes
841 =======================================================
842
843 >>> classes = model.to_classes(["Gene", "Protein", "Organism"])
844
845 This simply maps from a list of strings to a list of
846 classes in the calling model.
847
848 @raise ModelError: if the list of class names includes ones that don't exist
849
850 @rtype: list(L{intermine.model.Class})
851 """
852 return map(self.get_class, classnames)
853
854 - def column(self, path, *rest):
855 return Column(path, self, *rest)
856
859
861 """
862 Get a class by its name, or by a dotted path
863 ============================================
864
865 >>> model = Model("http://www.flymine.org/query/service/model")
866 >>> model.get_class("Gene")
867 <intermine.model.Class: Gene>
868 >>> model.get_class("Gene.proteins")
869 <intermine.model.Class: Protein>
870
871 This is the recommended way of retrieving a class from
872 the model. As well as handling class names, you can also
873 pass in a path such as "Gene.proteins" and get the
874 corresponding class back (<intermine.model.Class: Protein>)
875
876 @raise ModelError: if the class name refers to a non-existant object
877
878 @rtype: L{intermine.model.Class}
879 """
880 if name.find(".") != -1:
881 path = self.make_path(name)
882 if path.is_attribute():
883 raise ModelError("'" + str(path) + "' is not a class")
884 else:
885 return path.get_class()
886 if name in self.classes:
887 return self.classes[name]
888 else:
889 raise ModelError("'" + name + "' is not a class in this model")
890
892 """
893 Return a path object for the given path string
894 ==============================================
895
896 >>> path = Model.make_path("Gene.organism.name")
897 <intermine.model.Path: Gene.organism.name>
898
899 This is recommended manner of constructing path objects.
900
901 @type path: str
902 @type subclasses: dict
903
904 @raise PathParseError: if there is a problem parsing the path string
905
906 @rtype: L{intermine.model.Path}
907 """
908 return Path(path, self, subclasses)
909
911 """
912 Validate a path
913 ===============
914
915 >>> try:
916 ... model.validate_path("Gene.symbol")
917 ... return "path is valid"
918 ... except PathParseError:
919 ... return "path is invalid"
920 "path is valid"
921
922 When you don't need to interrogate relationships
923 between paths, simply using this method to validate
924 a path string is enough. It guarantees that there
925 is a descriptor for each section of the string,
926 with the appropriate relationships
927
928 @raise PathParseError: if there is a problem parsing the path string
929 """
930 try:
931 self.parse_path_string(path_string, subclasses)
932 return True
933 except PathParseError, e:
934 raise PathParseError("Error parsing '%s' (subclasses: %s)"
935 % ( path_string, str(subclasses) ), e )
936
938 """
939 Parse a path string into a list of descriptors - one for each section
940 =====================================================================
941
942 >>> parts = Model.parse_path_string(string)
943
944 This method is used when making paths from a model, and
945 when validating path strings. It probably won't need to
946 be called directly.
947
948 @see: L{intermine.model.Model.make_path}
949 @see: L{intermine.model.Model.validate_path}
950 @see: L{intermine.model.Path}
951 """
952 descriptors = []
953 names = path_string.split('.')
954 root_name = names.pop(0)
955
956 root_descriptor = self.get_class(root_name)
957 descriptors.append(root_descriptor)
958
959 if root_name in subclasses:
960 current_class = self.get_class(subclasses[root_name])
961 else:
962 current_class = root_descriptor
963
964 for field_name in names:
965 field = current_class.get_field(field_name)
966 descriptors.append(field)
967
968 if isinstance(field, Reference):
969 key = '.'.join(map(lambda x: x.name, descriptors))
970 if key in subclasses:
971 current_class = self.get_class(subclasses[key])
972 else:
973 current_class = field.type_class
974 else:
975 current_class = None
976
977 return descriptors
978
981
984
987
989
990 - def __init__(self, message, source, cause=None):
993
995 base = repr(self.message) + ":" + repr(self.source)
996 if self.cause is None:
997 return base
998 else:
999 return base + repr(self.cause)
1000