Документ взят из кэша поисковой машины. Адрес оригинального документа : http://kodomo.fbb.msu.ru/hg/allpy/raw-rev/d87129162eb4
Дата изменения: Unknown
Дата индексирования: Tue Oct 2 08:18:56 2012
Кодировка:

# HG changeset patch
# User Daniil Alexeyevsky
# Date 1310733783 -14400
# Node ID d87129162eb4709a9accf448e800142d3e87e6a5
# Parent 91e73fb1ac792cc6abbe7f1bb8ff4a5bb02f37ab
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.

diff -r 91e73fb1ac79 -r d87129162eb4 allpy/base.py
--- a/allpy/base.py Fri Jul 15 13:57:56 2011 +0400
+++ b/allpy/base.py Fri Jul 15 16:43:03 2011 +0400
@@ -101,7 +101,43 @@
def __ne__(self, other):
return not (self == other)

-class Sequence(list):
+class MarkupContainerMixin(object):
+ """Common functions for alignment and sequence for dealing with markups.
+ """
+
+ def _init(self):
+ """Hook to be called from __init__ of actual class."""
+ self.markups = {}
+
+ def add_markup(self, name, markup_class=None, **markup_kwargs):
+ """Create a markup object, add to self. Return the created markup.
+
+ - `name` is name for markup in `self.markups` dictionary
+ - optional `markup_class` is class for created markup
+ - optional keyword arguments are passed on to the markup constructor
+
+ For user markups you have to specify `name` and `markup_class`,
+ for the standard automatical markups just `name` is enough.
+ """
+ # We have to import markups here, and not in the module header
+ # so as not to create bad import loops.
+ # `base` module is used extensively in `markups` for inherinance,
+ # so breaking the loop here seems a lot easier.
+ import markups
+ if markup_class is None:
+ kind = self.kind + "_" + "markup"
+ markup_class = markups.by_name[kind, name]
+ assert name not in self.markups
+ markup = markup_class(self, name, caller='container', **markup_kwargs)
+ self.markups[name] = markup
+ return markup
+
+ def remove_markup(self, name):
+ """Remove markup."""
+ self.markups[name].remove()
+ del self.markups[name]
+
+class Sequence(list, MarkupContainerMixin):
"""Sequence of Monomers.

This behaves like list of monomer objects. In addition to standard list
@@ -117,13 +153,16 @@
types = base
"""Mapping of related types. SHOULD be redefined in subclasses."""

+ kind = 'sequence'
+ """Description of object kind."""
+
name = ''
description = ''
source = ''

def __init__(self, *args):
- self.markups = {}
list.__init__(self, *args)
+ MarkupContainerMixin._init(self)

@classmethod
def from_monomers(cls, monomers=[], name=None, description=None, source=None):
@@ -158,7 +197,7 @@
"""Hash sequence by identity."""
return id(self)

-class Alignment(object):
+class Alignment(MarkupContainerMixin):
"""Alignment. It is a list of Columns."""

types = base
@@ -167,11 +206,14 @@
sequences = None
"""Ordered list of sequences in alignment. Read, but DO NOT FIDDLE!"""

+ kind = 'alignment'
+ """Description of object kind."""
+
def __init__(self):
"""Initialize empty alignment."""
self.sequences = []
self.columns = []
- self.markups = {}
+ MarkupContainerMixin._init(self)

# Alignment grow & IO methods
# ==============================
@@ -507,22 +549,24 @@
name = None
"""Name of markup elements"""

- def _register(self, container, name):
- """Register self within container.
+ def __init__(self, container, name, **kwargs):
+ """Markup takes mandatory container and name and optional kwargs.

- Assure the name is not taken before. If name is not given, look in the
- class. Make sure we have some name at all.
+ Markups should never be created by the user. They are created by
+ Sequence or Alignment.
"""
- if name:
- self.name = name
- assert self.name is not None
- assert self.name not in container.markups
- container.markups[self.name] = self
+ self.name = name
+ assert kwargs.get('caller') == 'container', "Improper call"
+ self.refresh()

def refresh(self):
"""Recalculate markup values (if they are generated automatically)."""
pass

+ def remove(self):
+ """Remove the traces of markup object. Do not call this yourself!"""
+ pass
+
@classmethod
def from_record(cls, container, record, name=None):
"""Restore markup from `record`. (Used for loading from file).
@@ -533,7 +577,7 @@
Markup values should be stored in `record['markup']`, which is a list
of items separated with either `record['separator']` or a comma.
"""
- return cls(container, name)
+ return container.add_markup(name, markup_class=cls)

def to_record(self):
"""Save markup to `record`, for saving to file.
@@ -561,10 +605,14 @@

kind = 'sequence_markup'

- def __init__(self, sequence, name=None):
+ def __init__(self, sequence, name, **kwargs):
self.sequence = sequence
- self._register(sequence, name)
- self.refresh()
+ Markup.__init__(self, sequence, name, **kwargs)
+
+ def remove(self):
+ """Remove the traces of markup object. Do not call this yourself!"""
+ for monomer in self.monomers:
+ del self[monomer]

def sorted_keys(self):
"""Return list of monomers."""
@@ -592,6 +640,10 @@
"""Part of Mapping collection interface."""
return setattr(monomer, self.name, value)

+ def __delitem__(self, monomer):
+ """Part of Mapping collection interface."""
+ return delattr(monomer, self.name)
+
class AlignmentMarkup(dict, Markup):
"""Markupf for alignment.

@@ -601,10 +653,9 @@

kind = 'alignment_markup'

- def __init__(self, alignment, name=None):
+ def __init__(self, alignment, name, **kwargs):
self.alignment = alignment
- self._register(alignment, name)
- self.refresh()
+ Markup.__init__(self, alignment, name, **kwargs)

def sorted_keys(self):
"""Return a list of columns."""
diff -r 91e73fb1ac79 -r d87129162eb4 allpy/markups.py
--- a/allpy/markups.py Fri Jul 15 13:57:56 2011 +0400
+++ b/allpy/markups.py Fri Jul 15 16:43:03 2011 +0400
@@ -10,11 +10,16 @@
"""
# Add user classes if necessary
for markup_class in args:
- globals()[markup_class.__name__] = markup_class
+ class_name = markup_class.__name__
+ assert class_name not in globals(), "SameNamed markup already exists!"
+ globals()[class_name] = markup_class
# Update `by_name` dictonary
+ global by_name
+ by_name = {}
for markup_class in globals().values():
if hasattr(markup_class, 'name') and hasattr(markup_class, 'kind'):
fullname = markup_class.kind, markup_class.name
+ assert fullname not in by_name, "Samenamed markup already exists!"
by_name[fullname] = markup_class

class IntMarkupMixin(base.Markup):
@@ -22,7 +27,7 @@
@classmethod
def from_record(cls, container, record, name=None):
assert record['io-class'] == 'IntMarkup'
- result = cls(container, name=name)
+ result = container.add_markup(name, markup_class=cls)
separator = record.get('separator', ',')
values = record['markup'].split(separator)
assert len(values) == len(result.sorted_keys())
@@ -85,7 +90,7 @@
@classmethod
def from_record(cls, container, record, name=None):
assert record['io-class'] == 'SequenceCaseMarkup'
- result = cls(container, name=name)
+ result = container.add_markup(name, markup_class=cls)
markup = record['markup']
assert markup[0] == markup[-1] == "'"
markup = markup[1:-1]
diff -r 91e73fb1ac79 -r d87129162eb4 test/test_markups.py
--- a/test/test_markups.py Fri Jul 15 13:57:56 2011 +0400
+++ b/test/test_markups.py Fri Jul 15 16:43:03 2011 +0400
@@ -5,19 +5,19 @@

def test_index():
seq = protein.Sequence.from_string('SEQVENCE', name='sequence')
- markup = markups.SequenceIndexMarkup(seq)
+ markup = seq.add_markup('index')
assert seq[5].index == 5
assert markup[seq[5]] == 5

def test_number():
seq = protein.Sequence.from_string('SEQVENCE', name='sequence')
- markups.SequenceNumberMarkup(seq)
+ seq.add_markup('number')
assert seq[5].number == 6
assert seq.markups["number"][seq[5]] == 6

def test_case():
seq = protein.Sequence.from_string('seQVEncE', name='sequence')
- markups.SequenceCaseMarkup(seq)
+ seq.add_markup('case')
letters = ''.join(v[0] for v in seq.markups['case'].sorted_values())
assert letters == 'lluuullu'

@@ -33,20 +33,13 @@

def test_manual():
seq = protein.Sequence.from_string('SEQVENCE', name='sequence')
- try:
- base.SequenceMarkup(seq)
- except Exception:
- pass
- else:
- raise AssertionError("Here must be exception")
- markup = base.SequenceMarkup(seq, name='my_markup')
+ markup = seq.add_markup(name='my_markup', markup_class=base.SequenceMarkup)
markup[seq[4]] = 5
assert seq[4].my_markup == 5
seq[2].my_markup = 4
assert markup[seq[2]] == 4

def test_by_name():
- import sys
from_name = markups.by_name['sequence_markup', 'index']
assert from_name is markups.SequenceIndexMarkup
from_name = markups.by_name['alignment_markup', 'index']
@@ -59,18 +52,17 @@
)

class MySequenceMarkup(base.SequenceMarkup, markups.IntMarkupMixin):
- pass
+ name = 'm1'
class MyAlignmentMarkup(base.AlignmentMarkup, markups.IntMarkupMixin):
- pass
- markups.MySequenceMarkup = MySequenceMarkup
- markups.MyAlignmentMarkup = MyAlignmentMarkup
+ name = 'm2'
+ markups.update(MySequenceMarkup, MyAlignmentMarkup)

s = aln.sequences[0]
c = aln.columns
- m1 = MySequenceMarkup(s, name='m1')
+ m1 = s.add_markup('m1')
m1[s[5]] = 5
m1[s[3]] = 3
- m2 = MyAlignmentMarkup(aln, name='m2')
+ m2 = aln.add_markup('m2')
m2[c[5]] = 5
m2[c[6]] = 6