1# Copyright 2013 by Leighton Pritchard. All rights reserved. 2# 3# This file is part of the Biopython distribution and governed by your 4# choice of the "Biopython License Agreement" or the "BSD 3-Clause License". 5# Please see the LICENSE file that should have been included as part of this 6# package. 7 8"""Classes to represent a KGML Pathway Map. 9 10The KGML definition is as of release KGML v0.7.2 11(http://www.kegg.jp/kegg/xml/docs/) 12 13Classes: 14 - Pathway - Specifies graph information for the pathway map 15 - Relation - Specifies a relationship between two proteins or KOs, 16 or protein and compound. There is an implied direction to the 17 relationship in some cases. 18 - Reaction - A specific chemical reaction between a substrate and 19 a product. 20 - Entry - A node in the pathway graph 21 - Graphics - Entry subelement describing its visual representation 22 23""" 24 25import time 26from itertools import chain 27from xml.dom import minidom 28import xml.etree.ElementTree as ET 29 30 31# Pathway 32class Pathway: 33 """Represents a KGML pathway from KEGG. 34 35 Specifies graph information for the pathway map, as described in 36 release KGML v0.7.2 (http://www.kegg.jp/kegg/xml/docs/) 37 38 Attributes: 39 - name - KEGGID of the pathway map 40 - org - ko/ec/[org prefix] 41 - number - map number (integer) 42 - title - the map title 43 - image - URL of the image map for the pathway 44 - link - URL of information about the pathway 45 - entries - Dictionary of entries in the pathway, keyed by node ID 46 - reactions - Set of reactions in the pathway 47 48 The name attribute has a restricted format, so we make it a property and 49 enforce the formatting. 50 51 The Pathway object is the only allowed route for adding/removing 52 Entry, Reaction, or Relation elements. 53 54 Entries are held in a dictionary and keyed by the node ID for the 55 pathway graph - this allows for ready access via the Reaction/Relation 56 etc. elements. Entries must be added before reference by any other 57 element. 58 59 Reactions are held in a dictionary, keyed by node ID for the path. 60 The elements referred to in the reaction must be added before the 61 reaction itself. 62 63 """ 64 65 def __init__(self): 66 """Initialize the class.""" 67 self._name = "" 68 self.org = "" 69 self._number = None 70 self.title = "" 71 self.image = "" 72 self.link = "" 73 self.entries = {} 74 self._reactions = {} 75 self._relations = set() 76 77 def get_KGML(self): 78 """Return the pathway as a string in prettified KGML format.""" 79 header = "\n".join( 80 [ 81 '<?xml version="1.0"?>', 82 "<!DOCTYPE pathway SYSTEM " 83 '"http://www.genome.jp/kegg/xml/' 84 'KGML_v0.7.2_.dtd">', 85 "<!-- Created by KGML_Pathway.py %s -->" % time.asctime(), 86 ] 87 ) 88 rough_xml = header + ET.tostring(self.element, "utf-8").decode() 89 reparsed = minidom.parseString(rough_xml) 90 return reparsed.toprettyxml(indent=" ") 91 92 def add_entry(self, entry): 93 """Add an Entry element to the pathway.""" 94 # We insist that the node ID is an integer 95 if not isinstance(entry.id, int): 96 raise TypeError( 97 "Node ID must be an integer, got %s (%s)" % (type(entry.id), entry.id) 98 ) 99 entry._pathway = self # Let the entry know about the pathway 100 self.entries[entry.id] = entry 101 102 def remove_entry(self, entry): 103 """Remove an Entry element from the pathway.""" 104 if not isinstance(entry.id, int): 105 raise TypeError( 106 "Node ID must be an integer, got %s (%s)" % (type(entry.id), entry.id) 107 ) 108 # We need to remove the entry from any other elements that may 109 # contain it, which means removing those elements 110 # TODO 111 del self.entries[entry.id] 112 113 def add_reaction(self, reaction): 114 """Add a Reaction element to the pathway.""" 115 # We insist that the node ID is an integer and corresponds to an entry 116 if not isinstance(reaction.id, int): 117 raise ValueError( 118 "Node ID must be an integer, got %s (%s)" 119 % (type(reaction.id), reaction.id) 120 ) 121 if reaction.id not in self.entries: 122 raise ValueError("Reaction ID %d has no corresponding entry" % reaction.id) 123 reaction._pathway = self # Let the reaction know about the pathway 124 self._reactions[reaction.id] = reaction 125 126 def remove_reaction(self, reaction): 127 """Remove a Reaction element from the pathway.""" 128 if not isinstance(reaction.id, int): 129 raise TypeError( 130 "Node ID must be an integer, got %s (%s)" 131 % (type(reaction.id), reaction.id) 132 ) 133 # We need to remove the reaction from any other elements that may 134 # contain it, which means removing those elements 135 # TODO 136 del self._reactions[reaction.id] 137 138 def add_relation(self, relation): 139 """Add a Relation element to the pathway.""" 140 relation._pathway = self # Let the reaction know about the pathway 141 self._relations.add(relation) 142 143 def remove_relation(self, relation): 144 """Remove a Relation element from the pathway.""" 145 self._relations.remove(relation) 146 147 def __str__(self): 148 """Return a readable summary description string.""" 149 outstr = [ 150 "Pathway: %s" % self.title, 151 "KEGG ID: %s" % self.name, 152 "Image file: %s" % self.image, 153 "Organism: %s" % self.org, 154 "Entries: %d" % len(self.entries), 155 "Entry types:", 156 ] 157 for t in ["ortholog", "enzyme", "reaction", "gene", "group", "compound", "map"]: 158 etype = [e for e in self.entries.values() if e.type == t] 159 if len(etype): 160 outstr.append("\t%s: %d" % (t, len(etype))) 161 return "\n".join(outstr) + "\n" 162 163 # Assert correct formatting of the pathway name, and other attributes 164 def _getname(self): 165 return self._name 166 167 def _setname(self, value): 168 if not value.startswith("path:"): 169 raise ValueError("Pathway name should begin with 'path:', got %s" % value) 170 self._name = value 171 172 def _delname(self): 173 del self._name 174 175 name = property(_getname, _setname, _delname, "The KEGGID for the pathway map.") 176 177 def _getnumber(self): 178 return self._number 179 180 def _setnumber(self, value): 181 self._number = int(value) 182 183 def _delnumber(self): 184 del self._number 185 186 number = property(_getnumber, _setnumber, _delnumber, "The KEGG map number.") 187 188 @property 189 def compounds(self): 190 """Get a list of entries of type compound.""" 191 return [e for e in self.entries.values() if e.type == "compound"] 192 193 @property 194 def maps(self): 195 """Get a list of entries of type map.""" 196 return [e for e in self.entries.values() if e.type == "map"] 197 198 @property 199 def orthologs(self): 200 """Get a list of entries of type ortholog.""" 201 return [e for e in self.entries.values() if e.type == "ortholog"] 202 203 @property 204 def genes(self): 205 """Get a list of entries of type gene.""" 206 return [e for e in self.entries.values() if e.type == "gene"] 207 208 @property 209 def reactions(self): 210 """Get a list of reactions in the pathway.""" 211 return self._reactions.values() 212 213 @property 214 def reaction_entries(self): 215 """List of entries corresponding to each reaction in the pathway.""" 216 return [self.entries[i] for i in self._reactions] 217 218 @property 219 def relations(self): 220 """Get a list of relations in the pathway.""" 221 return list(self._relations) 222 223 @property 224 def element(self): 225 """Return the Pathway as a valid KGML element.""" 226 # The root is this Pathway element 227 pathway = ET.Element("pathway") 228 pathway.attrib = { 229 "name": self._name, 230 "org": self.org, 231 "number": str(self._number), 232 "title": self.title, 233 "image": self.image, 234 "link": self.link, 235 } 236 # We add the Entries in node ID order 237 for eid, entry in sorted(self.entries.items()): 238 pathway.append(entry.element) 239 # Next we add Relations 240 for relation in self._relations: 241 pathway.append(relation.element) 242 for eid, reaction in sorted(self._reactions.items()): 243 pathway.append(reaction.element) 244 return pathway 245 246 @property 247 def bounds(self): 248 """Coordinate bounds for all Graphics elements in the Pathway. 249 250 Returns the [(xmin, ymin), (xmax, ymax)] coordinates for all 251 Graphics elements in the Pathway 252 """ 253 xlist, ylist = [], [] 254 for b in [g.bounds for g in self.entries.values()]: 255 xlist.extend([b[0][0], b[1][0]]) 256 ylist.extend([b[0][1], b[1][1]]) 257 return [(min(xlist), min(ylist)), (max(xlist), max(ylist))] 258 259 260# Entry 261class Entry: 262 """Represent an Entry from KGML. 263 264 Each Entry element is a node in the pathway graph, as described in 265 release KGML v0.7.2 (http://www.kegg.jp/kegg/xml/docs/) 266 267 Attributes: 268 - id - The ID of the entry in the pathway map (integer) 269 - names - List of KEGG IDs for the entry 270 - type - The type of the entry 271 - link - URL of information about the entry 272 - reaction - List of KEGG IDs of the corresponding reactions 273 (integer) 274 - graphics - List of Graphics objects describing the Entry's visual 275 representation 276 - components - List of component node ID for this Entry ('group') 277 - alt - List of alternate names for the Entry 278 279 NOTE: The alt attribute represents a subelement of the substrate and 280 product elements in the KGML file 281 282 """ 283 284 def __init__(self): 285 """Initialize the class.""" 286 self._id = None 287 self._names = [] 288 self.type = "" 289 self.image = "" 290 self.link = "" 291 self.graphics = [] 292 self.components = set() 293 self.alt = [] 294 self._pathway = None 295 self._reactions = [] 296 297 def __str__(self): 298 """Return readable descriptive string.""" 299 outstr = [ 300 "Entry node ID: %d" % self.id, 301 "Names: %s" % self.name, 302 "Type: %s" % self.type, 303 "Components: %s" % self.components, 304 "Reactions: %s" % self.reaction, 305 "Graphics elements: %d %s" % (len(self.graphics), self.graphics), 306 ] 307 return "\n".join(outstr) + "\n" 308 309 def add_component(self, element): 310 """Add an element to the entry. 311 312 If the Entry is already part of a pathway, make sure 313 the component already exists. 314 """ 315 if self._pathway is not None: 316 if element.id not in self._pathway.entries: 317 raise ValueError( 318 "Component %s is not an entry in the pathway" % element.id 319 ) 320 self.components.add(element) 321 322 def remove_component(self, value): 323 """Remove the entry with the passed ID from the group.""" 324 self.components.remove(value) 325 326 def add_graphics(self, entry): 327 """Add the Graphics entry.""" 328 self.graphics.append(entry) 329 330 def remove_graphics(self, entry): 331 """Remove the Graphics entry with the passed ID from the group.""" 332 self.graphics.remove(entry) 333 334 # Names may be given as a space-separated list of KEGG identifiers 335 def _getname(self): 336 return " ".join(self._names) 337 338 def _setname(self, value): 339 self._names = value.split() 340 341 def _delname(self): 342 self._names = [] 343 344 name = property( 345 _getname, _setname, _delname, "List of KEGG identifiers for the Entry." 346 ) 347 348 # Reactions may be given as a space-separated list of KEGG identifiers 349 def _getreaction(self): 350 return " ".join(self._reactions) 351 352 def _setreaction(self, value): 353 self._reactions = value.split() 354 355 def _delreaction(self): 356 self._reactions = [] 357 358 reaction = property( 359 _getreaction, 360 _setreaction, 361 _delreaction, 362 "List of reaction KEGG IDs for this Entry.", 363 ) 364 365 # We make sure that the node ID is an integer 366 def _getid(self): 367 return self._id 368 369 def _setid(self, value): 370 self._id = int(value) 371 372 def _delid(self): 373 del self._id 374 375 id = property(_getid, _setid, _delid, "The pathway graph node ID for the Entry.") 376 377 @property 378 def element(self): 379 """Return the Entry as a valid KGML element.""" 380 # The root is this Entry element 381 entry = ET.Element("entry") 382 entry.attrib = { 383 "id": str(self._id), 384 "name": self.name, 385 "link": self.link, 386 "type": self.type, 387 } 388 if len(self._reactions): 389 entry.attrib["reaction"] = self.reaction 390 if len(self.graphics): 391 for g in self.graphics: 392 entry.append(g.element) 393 if len(self.components): 394 for c in self.components: 395 entry.append(c.element) 396 return entry 397 398 @property 399 def bounds(self): 400 """Coordinate bounds for all Graphics elements in the Entry. 401 402 Return the [(xmin, ymin), (xmax, ymax)] co-ordinates for the Entry 403 Graphics elements. 404 """ 405 xlist, ylist = [], [] 406 for b in [g.bounds for g in self.graphics]: 407 xlist.extend([b[0][0], b[1][0]]) 408 ylist.extend([b[0][1], b[1][1]]) 409 return [(min(xlist), min(ylist)), (max(xlist), max(ylist))] 410 411 @property 412 def is_reactant(self): 413 """Return true if this Entry participates in any reaction in its parent pathway.""" 414 for rxn in self._pathway.reactions: 415 if self._id in rxn.reactant_ids: 416 return True 417 return False 418 419 420# Component 421class Component: 422 """An Entry subelement used to represents a complex node. 423 424 A subelement of the Entry element, used when the Entry is a complex 425 node, as described in release KGML v0.7.2 426 (http://www.kegg.jp/kegg/xml/docs/) 427 428 The Component acts as a collection (with type 'group', and typically 429 its own Graphics subelement), having only an ID. 430 """ 431 432 def __init__(self, parent): 433 """Initialize the class.""" 434 self._id = None 435 self._parent = parent 436 437 # We make sure that the node ID is an integer 438 def _getid(self): 439 return self._id 440 441 def _setid(self, value): 442 self._id = int(value) 443 444 def _delid(self): 445 del self._id 446 447 id = property(_getid, _setid, _delid, "The pathway graph node ID for the Entry") 448 449 @property 450 def element(self): 451 """Return the Component as a valid KGML element.""" 452 # The root is this Component element 453 component = ET.Element("component") 454 component.attrib = {"id": str(self._id)} 455 return component 456 457 458# Graphics 459class Graphics: 460 """An Entry subelement used to represents the visual representation. 461 462 A subelement of Entry, specifying its visual representation, as 463 described in release KGML v0.7.2 (http://www.kegg.jp/kegg/xml/docs/) 464 465 Attributes: 466 - name Label for the graphics object 467 - x X-axis position of the object (int) 468 - y Y-axis position of the object (int) 469 - coords polyline co-ordinates, list of (int, int) tuples 470 - type object shape 471 - width object width (int) 472 - height object height (int) 473 - fgcolor object foreground color (hex RGB) 474 - bgcolor object background color (hex RGB) 475 476 Some attributes are present only for specific graphics types. For 477 example, line types do not (typically) have a width. 478 We permit non-DTD attributes and attribute settings, such as 479 480 dash List of ints, describing an on/off pattern for dashes 481 482 """ 483 484 def __init__(self, parent): 485 """Initialize the class.""" 486 self.name = "" 487 self._x = None 488 self._y = None 489 self._coords = None 490 self.type = "" 491 self._width = None 492 self._height = None 493 self.fgcolor = "" 494 self.bgcolor = "" 495 self._parent = parent 496 497 # We make sure that the XY coordinates, width and height are numbers 498 def _getx(self): 499 return self._x 500 501 def _setx(self, value): 502 self._x = float(value) 503 504 def _delx(self): 505 del self._x 506 507 x = property(_getx, _setx, _delx, "The X coordinate for the graphics element.") 508 509 def _gety(self): 510 return self._y 511 512 def _sety(self, value): 513 self._y = float(value) 514 515 def _dely(self): 516 del self._y 517 518 y = property(_gety, _sety, _dely, "The Y coordinate for the graphics element.") 519 520 def _getwidth(self): 521 return self._width 522 523 def _setwidth(self, value): 524 self._width = float(value) 525 526 def _delwidth(self): 527 del self._width 528 529 width = property( 530 _getwidth, _setwidth, _delwidth, "The width of the graphics element." 531 ) 532 533 def _getheight(self): 534 return self._height 535 536 def _setheight(self, value): 537 self._height = float(value) 538 539 def _delheight(self): 540 del self._height 541 542 height = property( 543 _getheight, _setheight, _delheight, "The height of the graphics element." 544 ) 545 546 # We make sure that the polyline co-ordinates are integers, too 547 def _getcoords(self): 548 return self._coords 549 550 def _setcoords(self, value): 551 clist = [int(e) for e in value.split(",")] 552 self._coords = [tuple(clist[i : i + 2]) for i in range(0, len(clist), 2)] 553 554 def _delcoords(self): 555 del self._coords 556 557 coords = property( 558 _getcoords, 559 _setcoords, 560 _delcoords, 561 "Polyline coordinates for the graphics element.", 562 ) 563 564 # Set default colors 565 def _getfgcolor(self): 566 return self._fgcolor 567 568 def _setfgcolor(self, value): 569 if value == "none": 570 self._fgcolor = "#000000" # this default defined in KGML spec 571 else: 572 self._fgcolor = value 573 574 def _delfgcolor(self): 575 del self._fgcolor 576 577 fgcolor = property(_getfgcolor, _setfgcolor, _delfgcolor, "Foreground color.") 578 579 def _getbgcolor(self): 580 return self._bgcolor 581 582 def _setbgcolor(self, value): 583 if value == "none": 584 self._bgcolor = "#000000" # this default defined in KGML spec 585 else: 586 self._bgcolor = value 587 588 def _delbgcolor(self): 589 del self._bgcolor 590 591 bgcolor = property(_getbgcolor, _setbgcolor, _delbgcolor, "Background color.") 592 593 @property 594 def element(self): 595 """Return the Graphics as a valid KGML element.""" 596 # The root is this Component element 597 graphics = ET.Element("graphics") 598 if isinstance(self.fgcolor, str): # Assumes that string is hexstring 599 fghex = self.fgcolor 600 else: # Assumes ReportLab Color object 601 fghex = "#" + self.fgcolor.hexval()[2:] 602 if isinstance(self.bgcolor, str): # Assumes that string is hexstring 603 bghex = self.bgcolor 604 else: # Assumes ReportLab Color object 605 bghex = "#" + self.bgcolor.hexval()[2:] 606 graphics.attrib = { 607 "name": self.name, 608 "type": self.type, 609 "fgcolor": fghex, 610 "bgcolor": bghex, 611 } 612 for (n, attr) in [ 613 ("x", "_x"), 614 ("y", "_y"), 615 ("width", "_width"), 616 ("height", "_height"), 617 ]: 618 if getattr(self, attr) is not None: 619 graphics.attrib[n] = str(getattr(self, attr)) 620 if self.type == "line": # Need to write polycoords 621 graphics.attrib["coords"] = ",".join( 622 [str(e) for e in chain.from_iterable(self.coords)] 623 ) 624 return graphics 625 626 @property 627 def bounds(self): 628 """Coordinate bounds for the Graphics element. 629 630 Return the bounds of the Graphics object as an [(xmin, ymin), 631 (xmax, ymax)] tuple. Co-ordinates give the centre of the 632 circle, rectangle, roundrectangle elements, so we have to 633 adjust for the relevant width/height. 634 """ 635 if self.type == "line": 636 xlist = [x for x, y in self.coords] 637 ylist = [y for x, y in self.coords] 638 return [(min(xlist), min(ylist)), (max(xlist), max(ylist))] 639 else: 640 return [ 641 (self.x - self.width * 0.5, self.y - self.height * 0.5), 642 (self.x + self.width * 0.5, self.y + self.height * 0.5), 643 ] 644 645 @property 646 def centre(self): 647 """Return the centre of the Graphics object as an (x, y) tuple.""" 648 return ( 649 0.5 * (self.bounds[0][0] + self.bounds[1][0]), 650 0.5 * (self.bounds[0][1] + self.bounds[1][1]), 651 ) 652 653 654# Reaction 655class Reaction: 656 """A specific chemical reaction with substrates and products. 657 658 This describes a specific chemical reaction between one or more 659 substrates and one or more products. 660 661 Attributes: 662 - id Pathway graph node ID of the entry 663 - names List of KEGG identifier(s) from the REACTION database 664 - type String: reversible or irreversible 665 - substrate Entry object of the substrate 666 - product Entry object of the product 667 668 """ 669 670 def __init__(self): 671 """Initialize the class.""" 672 self._id = None 673 self._names = [] 674 self.type = "" 675 self._substrates = set() 676 self._products = set() 677 self._pathway = None 678 679 def __str__(self): 680 """Return an informative human-readable string.""" 681 outstr = [ 682 "Reaction node ID: %s" % self.id, 683 "Reaction KEGG IDs: %s" % self.name, 684 "Type: %s" % self.type, 685 "Substrates: %s" % ",".join([s.name for s in self.substrates]), 686 "Products: %s" % ",".join([s.name for s in self.products]), 687 ] 688 return "\n".join(outstr) + "\n" 689 690 def add_substrate(self, substrate_id): 691 """Add a substrate, identified by its node ID, to the reaction.""" 692 if self._pathway is not None: 693 if int(substrate_id) not in self._pathway.entries: 694 raise ValueError( 695 "Couldn't add substrate, no node ID %d in Pathway" 696 % int(substrate_id) 697 ) 698 self._substrates.add(substrate_id) 699 700 def add_product(self, product_id): 701 """Add a product, identified by its node ID, to the reaction.""" 702 if self._pathway is not None: 703 if int(product_id) not in self._pathway.entries: 704 raise ValueError( 705 "Couldn't add product, no node ID %d in Pathway" % product_id 706 ) 707 self._products.add(int(product_id)) 708 709 # The node ID is also the node ID of the Entry that corresponds to the 710 # reaction; we get the corresponding Entry when there is an associated 711 # Pathway 712 def _getid(self): 713 return self._id 714 715 def _setid(self, value): 716 self._id = int(value) 717 718 def _delid(self): 719 del self._id 720 721 id = property(_getid, _setid, _delid, "Node ID for the reaction.") 722 723 # Names may show up as a space-separated list of several KEGG identifiers 724 def _getnames(self): 725 return " ".join(self._names) 726 727 def _setnames(self, value): 728 self._names.extend(value.split()) 729 730 def _delnames(self): 731 del self.names 732 733 name = property( 734 _getnames, _setnames, _delnames, "List of KEGG identifiers for the reaction." 735 ) 736 737 # products and substrates are read-only properties, returning lists 738 # of Entry objects 739 @property 740 def substrates(self): 741 """Return list of substrate Entry elements.""" 742 return [self._pathway.entries[sid] for sid in self._substrates] 743 744 @property 745 def products(self): 746 """Return list of product Entry elements.""" 747 return [self._pathway.entries[pid] for pid in self._products] 748 749 @property 750 def entry(self): 751 """Return the Entry corresponding to this reaction.""" 752 return self._pathway.entries[self._id] 753 754 @property 755 def reactant_ids(self): 756 """Return a list of substrate and product reactant IDs.""" 757 return self._products.union(self._substrates) 758 759 @property 760 def element(self): 761 """Return KGML element describing the Reaction.""" 762 # The root is this Relation element 763 reaction = ET.Element("reaction") 764 reaction.attrib = {"id": str(self.id), "name": self.name, "type": self.type} 765 for s in self._substrates: 766 substrate = ET.Element("substrate") 767 substrate.attrib["id"] = str(s) 768 substrate.attrib["name"] = self._pathway.entries[s].name 769 reaction.append(substrate) 770 for p in self._products: 771 product = ET.Element("product") 772 product.attrib["id"] = str(p) 773 product.attrib["name"] = self._pathway.entries[p].name 774 reaction.append(product) 775 return reaction 776 777 778# Relation 779class Relation: 780 """A relationship between to products, KOs, or protein and compound. 781 782 This describes a relationship between two products, KOs, or protein 783 and compound, as described in release KGML v0.7.2 784 (http://www.kegg.jp/kegg/xml/docs/) 785 786 Attributes: 787 - entry1 - The first Entry object node ID defining the 788 relation (int) 789 - entry2 - The second Entry object node ID defining the 790 relation (int) 791 - type - The relation type 792 - subtypes - List of subtypes for the relation, as a list of 793 (name, value) tuples 794 795 """ 796 797 def __init__(self): 798 """Initialize the class.""" 799 self._entry1 = None 800 self._entry2 = None 801 self.type = "" 802 self.subtypes = [] 803 self._pathway = None 804 805 def __str__(self): 806 """Return a useful human-readable string.""" 807 outstr = [ 808 "Relation (subtypes: %d):" % len(self.subtypes), 809 "Entry1:", 810 str(self.entry1), 811 "Entry2:", 812 str(self.entry2), 813 ] 814 for s in self.subtypes: 815 outstr.extend(["Subtype: %s" % s[0], str(s[1])]) 816 return "\n".join(outstr) 817 818 # Properties entry1 and entry2 819 def _getentry1(self): 820 if self._pathway is not None: 821 return self._pathway.entries[self._entry1] 822 return self._entry1 823 824 def _setentry1(self, value): 825 self._entry1 = int(value) 826 827 def _delentry1(self): 828 del self._entry1 829 830 entry1 = property(_getentry1, _setentry1, _delentry1, "Entry1 of the relation.") 831 832 def _getentry2(self): 833 if self._pathway is not None: 834 return self._pathway.entries[self._entry2] 835 return self._entry2 836 837 def _setentry2(self, value): 838 self._entry2 = int(value) 839 840 def _delentry2(self): 841 del self._entry2 842 843 entry2 = property(_getentry2, _setentry2, _delentry2, "Entry2 of the relation.") 844 845 @property 846 def element(self): 847 """Return KGML element describing the Relation.""" 848 # The root is this Relation element 849 relation = ET.Element("relation") 850 relation.attrib = { 851 "entry1": str(self._entry1), 852 "entry2": str(self._entry2), 853 "type": self.type, 854 } 855 for (name, value) in self.subtypes: 856 subtype = ET.Element("subtype") 857 subtype.attrib = {"name": name, "value": str(value)} 858 relation.append(subtype) 859 return relation 860