Package intermine :: Module model
[hide private]
[frames] | no frames]

Source Code for Module intermine.model

   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" 
20 21 -class Field(object):
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
84 - def __repr__(self):
85 return self.name + " is a " + self.type_name
86
87 - def __str__(self):
88 return self.name
89 90 @property
91 - def fieldtype(self):
92 raise Exception("Fields should never be directly instantiated")
93
94 -class Attribute(Field):
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
103 - def fieldtype(self):
104 return "attribute"
105
106 -class Reference(Field):
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
133 - def __repr__(self):
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
147 - def fieldtype(self):
148 return "reference"
149
150 -class Collection(Reference):
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 """
157 - def __repr__(self):
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
166 - def fieldtype(self):
167 return "collection"
168
169 -class Class(object):
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 # All InterMineObject classes have an id attribute. 226 id_field = Attribute("id", "Integer", self) 227 self.field_dict["id"] = id_field
228
229 - def __repr__(self):
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
234 - def fields(self):
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
246 - def __iter__(self):
247 for f in self.field_dict.values(): 248 yield f
249
250 - def __contains__(self, item):
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
257 - def attributes(self):
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
267 - def references(self):
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
278 - def collections(self):
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
287 - def get_field(self, name):
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
387 - def __str__(self):
388 return self._string
389
390 - def __repr__(self):
391 return '<' + self.__module__ + "." + self.__class__.__name__ + ": " + self._string + '>'
392
393 - def prefix(self):
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
411 - def append(self, *elements):
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
427 - def root(self):
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
436 - def end(self):
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
444 - def get_class(self):
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
462 - def is_reference(self):
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
471 - def is_class(self):
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
479 - def is_attribute(self):
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
487 - def __eq__(self, other):
488 return str(self) == str(other)
489
490 - def __hash__(self):
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
494 -class ConstraintTree(object):
495
496 - def __init__(self, op, left, right):
497 self.op = op 498 self.left = left 499 self.right = right
500
501 - def __and__(self, other):
502 return ConstraintTree('AND', self, other)
503
504 - def __or__(self, other):
505 return ConstraintTree('OR', self, other)
506
507 - def __iter__(self):
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
517 -class ConstraintNode(ConstraintTree):
518
519 - def __init__(self, *args, **kwargs):
520 self.vargs = args 521 self.kwargs = kwargs
522
523 - def __iter__(self):
524 yield self
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
531 -class CodelessNode(ConstraintNode):
532
533 - def as_logic(self, code = None, start = 'A'):
534 return ''
535
536 -class Column(object):
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 # alias 551 if isinstance(path, Path): 552 self._path = path 553 else: 554 self._path = model.make_path(path, subclasses) 555 self._branches = {}
556
557 - def select(self, *cols):
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
579 - def __len__(self):
580 """ 581 Return the number of values in this column. 582 """ 583 return self.select().count()
584
585 - def __iter__(self):
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
600 - def __getattr__(self, name):
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
614 - def __str__(self):
615 return str(self._path)
616
617 - def __mod__(self, other):
618 if isinstance(other, tuple): 619 return ConstraintNode(str(self), 'LOOKUP', *other) 620 else: 621 return ConstraintNode(str(self), 'LOOKUP', str(other))
622
623 - def __rshift__(self, other):
624 return CodelessNode(str(self), str(other))
625 626 __lshift__ = __rshift__ 627
628 - def __eq__(self, other):
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
640 - def __ne__(self, other):
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
652 - def __xor__(self, other):
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
666 - def __lt__(self, other):
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
676 - def __le__(self, other):
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
684 - def __gt__(self, other):
685 return ConstraintNode(str(self), ">", other)
686
687 - def __ge__(self, other):
688 return ConstraintNode(str(self), ">=", other)
689
690 -class Model(object):
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 # Make sugary aliases 740 self.table = self.column
741
742 - def parse_model(self, source):
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
798 - def vivify(self):
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
819 - def to_ancestry(self, cd):
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 # weeds out the java classes 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
838 - def to_classes(self, classnames):
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
857 - def __getattr__(self, name):
858 return self.column(name)
859
860 - def get_class(self, name):
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
891 - def make_path(self, path, subclasses={}):
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
910 - def validate_path(self, path_string, subclasses={}):
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
937 - def parse_path_string(self, path_string, subclasses={}):
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
979 - def _unproxied(self):
980 return self
981
982 -class ModelError(ReadableException):
983 pass
984
985 -class PathParseError(ModelError):
986 pass
987
988 -class ModelParseError(ModelError):
989
990 - def __init__(self, message, source, cause=None):
991 self.source = source 992 super(ModelParseError, self).__init__(message, cause)
993
994 - def __str__(self):
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