allpy
diff allpy/base.py @ 822:d87129162eb4
Implemented & tested new markup API. See #95
1) Sequences, Alignment and Blocks now have two new methods:
- add_markup(name, markup_class=optional, **kwargs=optional)
- remove_markup(name)
name refers to the same name as in aln.markups[name] or sequence[i].name
It is now explicitly denied to create markups any other way.
2) Markups now have `remove()` method that means 'release all memory that would
not be released otherwised, if we just remove markup from the dictionary'. For
sequences markups it removes markup attribute from each monomer.
3) Added necessary del sequence_markup[monomer] method.
4) Many base classes have attribute `kind`; for Alignments and Blocks it is
'alignment', for Sequences it is 'sequence' for AlignmentMarkups it is
'alignment_markup' for SequenceMarkups it is 'sequence_markup'. This attribute
is crucial for new alignment construction API.
5) Common stuff for MarkupContainers (Alignments and Sequences) is in
MarkupContainerMixin.
author | Daniil Alexeyevsky <dendik@kodomo.fbb.msu.ru> |
---|---|
date | Fri, 15 Jul 2011 16:43:03 +0400 |
parents | 91e73fb1ac79 |
children | 0192c5c09ce8 |
line diff
1.1 --- a/allpy/base.py Fri Jul 15 13:57:56 2011 +0400 1.2 +++ b/allpy/base.py Fri Jul 15 16:43:03 2011 +0400 1.3 @@ -101,7 +101,43 @@ 1.4 def __ne__(self, other): 1.5 return not (self == other) 1.6 1.7 -class Sequence(list): 1.8 +class MarkupContainerMixin(object): 1.9 + """Common functions for alignment and sequence for dealing with markups. 1.10 + """ 1.11 + 1.12 + def _init(self): 1.13 + """Hook to be called from __init__ of actual class.""" 1.14 + self.markups = {} 1.15 + 1.16 + def add_markup(self, name, markup_class=None, **markup_kwargs): 1.17 + """Create a markup object, add to self. Return the created markup. 1.18 + 1.19 + - `name` is name for markup in `self.markups` dictionary 1.20 + - optional `markup_class` is class for created markup 1.21 + - optional keyword arguments are passed on to the markup constructor 1.22 + 1.23 + For user markups you have to specify `name` and `markup_class`, 1.24 + for the standard automatical markups just `name` is enough. 1.25 + """ 1.26 + # We have to import markups here, and not in the module header 1.27 + # so as not to create bad import loops. 1.28 + # `base` module is used extensively in `markups` for inherinance, 1.29 + # so breaking the loop here seems a lot easier. 1.30 + import markups 1.31 + if markup_class is None: 1.32 + kind = self.kind + "_" + "markup" 1.33 + markup_class = markups.by_name[kind, name] 1.34 + assert name not in self.markups 1.35 + markup = markup_class(self, name, caller='container', **markup_kwargs) 1.36 + self.markups[name] = markup 1.37 + return markup 1.38 + 1.39 + def remove_markup(self, name): 1.40 + """Remove markup.""" 1.41 + self.markups[name].remove() 1.42 + del self.markups[name] 1.43 + 1.44 +class Sequence(list, MarkupContainerMixin): 1.45 """Sequence of Monomers. 1.46 1.47 This behaves like list of monomer objects. In addition to standard list 1.48 @@ -117,13 +153,16 @@ 1.49 types = base 1.50 """Mapping of related types. SHOULD be redefined in subclasses.""" 1.51 1.52 + kind = 'sequence' 1.53 + """Description of object kind.""" 1.54 + 1.55 name = '' 1.56 description = '' 1.57 source = '' 1.58 1.59 def __init__(self, *args): 1.60 - self.markups = {} 1.61 list.__init__(self, *args) 1.62 + MarkupContainerMixin._init(self) 1.63 1.64 @classmethod 1.65 def from_monomers(cls, monomers=[], name=None, description=None, source=None): 1.66 @@ -158,7 +197,7 @@ 1.67 """Hash sequence by identity.""" 1.68 return id(self) 1.69 1.70 -class Alignment(object): 1.71 +class Alignment(MarkupContainerMixin): 1.72 """Alignment. It is a list of Columns.""" 1.73 1.74 types = base 1.75 @@ -167,11 +206,14 @@ 1.76 sequences = None 1.77 """Ordered list of sequences in alignment. Read, but DO NOT FIDDLE!""" 1.78 1.79 + kind = 'alignment' 1.80 + """Description of object kind.""" 1.81 + 1.82 def __init__(self): 1.83 """Initialize empty alignment.""" 1.84 self.sequences = [] 1.85 self.columns = [] 1.86 - self.markups = {} 1.87 + MarkupContainerMixin._init(self) 1.88 1.89 # Alignment grow & IO methods 1.90 # ============================== 1.91 @@ -507,22 +549,24 @@ 1.92 name = None 1.93 """Name of markup elements""" 1.94 1.95 - def _register(self, container, name): 1.96 - """Register self within container. 1.97 + def __init__(self, container, name, **kwargs): 1.98 + """Markup takes mandatory container and name and optional kwargs. 1.99 1.100 - Assure the name is not taken before. If name is not given, look in the 1.101 - class. Make sure we have some name at all. 1.102 + Markups should never be created by the user. They are created by 1.103 + Sequence or Alignment. 1.104 """ 1.105 - if name: 1.106 - self.name = name 1.107 - assert self.name is not None 1.108 - assert self.name not in container.markups 1.109 - container.markups[self.name] = self 1.110 + self.name = name 1.111 + assert kwargs.get('caller') == 'container', "Improper call" 1.112 + self.refresh() 1.113 1.114 def refresh(self): 1.115 """Recalculate markup values (if they are generated automatically).""" 1.116 pass 1.117 1.118 + def remove(self): 1.119 + """Remove the traces of markup object. Do not call this yourself!""" 1.120 + pass 1.121 + 1.122 @classmethod 1.123 def from_record(cls, container, record, name=None): 1.124 """Restore markup from `record`. (Used for loading from file). 1.125 @@ -533,7 +577,7 @@ 1.126 Markup values should be stored in `record['markup']`, which is a list 1.127 of items separated with either `record['separator']` or a comma. 1.128 """ 1.129 - return cls(container, name) 1.130 + return container.add_markup(name, markup_class=cls) 1.131 1.132 def to_record(self): 1.133 """Save markup to `record`, for saving to file. 1.134 @@ -561,10 +605,14 @@ 1.135 1.136 kind = 'sequence_markup' 1.137 1.138 - def __init__(self, sequence, name=None): 1.139 + def __init__(self, sequence, name, **kwargs): 1.140 self.sequence = sequence 1.141 - self._register(sequence, name) 1.142 - self.refresh() 1.143 + Markup.__init__(self, sequence, name, **kwargs) 1.144 + 1.145 + def remove(self): 1.146 + """Remove the traces of markup object. Do not call this yourself!""" 1.147 + for monomer in self.monomers: 1.148 + del self[monomer] 1.149 1.150 def sorted_keys(self): 1.151 """Return list of monomers.""" 1.152 @@ -592,6 +640,10 @@ 1.153 """Part of Mapping collection interface.""" 1.154 return setattr(monomer, self.name, value) 1.155 1.156 + def __delitem__(self, monomer): 1.157 + """Part of Mapping collection interface.""" 1.158 + return delattr(monomer, self.name) 1.159 + 1.160 class AlignmentMarkup(dict, Markup): 1.161 """Markupf for alignment. 1.162 1.163 @@ -601,10 +653,9 @@ 1.164 1.165 kind = 'alignment_markup' 1.166 1.167 - def __init__(self, alignment, name=None): 1.168 + def __init__(self, alignment, name, **kwargs): 1.169 self.alignment = alignment 1.170 - self._register(alignment, name) 1.171 - self.refresh() 1.172 + Markup.__init__(self, alignment, name, **kwargs) 1.173 1.174 def sorted_keys(self): 1.175 """Return a list of columns."""