Spaces:
No application file
No application file
| # Copyright 2006-2021 by Peter Cock. | |
| # Copyright 2022 by Michiel de Hoon. | |
| # All rights reserved. | |
| # | |
| # This file is part of the Biopython distribution and governed by your | |
| # choice of the "Biopython License Agreement" or the "BSD 3-Clause License". | |
| # Please see the LICENSE file that should have been included as part of this | |
| # package. | |
| """Bio.Align support module (not for general use). | |
| Unless you are writing a new parser or writer for Bio.Align, you should not | |
| use this module. It provides base classes to try and simplify things. | |
| """ | |
| from abc import ABC | |
| from abc import abstractmethod | |
| from Bio import StreamModeError | |
| class AlignmentIterator(ABC): | |
| """Base class for building Alignment iterators. | |
| You should write a parse method that returns an Alignment generator. You | |
| may wish to redefine the __init__ method as well. | |
| Subclasses may define the following class attributes: | |
| - mode - 't' or 'b' for text or binary files, respectively | |
| - fmt - a human-readable name for the file format. | |
| """ | |
| mode = "t" # assume text files by default | |
| fmt = None # to be defined in the subclass | |
| def __init__(self, source): | |
| """Create an AlignmentIterator object. | |
| Arguments: | |
| - source - input file stream, or path to input file | |
| This method MAY be overridden by any subclass. | |
| Note when subclassing: | |
| - there should be a single non-optional argument, the source. | |
| - you can add additional optional arguments. | |
| """ | |
| self.source = source | |
| try: | |
| self._stream = open(source, "r" + self.mode) | |
| except TypeError: # not a path, assume we received a stream | |
| if self.mode == "t": | |
| if source.read(0) != "": | |
| raise StreamModeError( | |
| f"{self.fmt} files must be opened in text mode." | |
| ) from None | |
| elif self.mode == "b": | |
| if source.read(0) != b"": | |
| raise StreamModeError( | |
| f"{self.fmt} files must be opened in binary mode." | |
| ) from None | |
| else: | |
| raise ValueError(f"Unknown mode '{self.mode}'") from None | |
| self._stream = source | |
| try: | |
| self._read_header(self._stream) | |
| except Exception: | |
| self._close() | |
| raise | |
| def __next__(self): | |
| """Return the next entry.""" | |
| try: | |
| stream = self._stream | |
| except AttributeError: | |
| raise StopIteration from None | |
| try: | |
| alignment = self._read_next_alignment(stream) | |
| if alignment is None: | |
| raise StopIteration | |
| except Exception: | |
| self._close() | |
| raise | |
| return alignment | |
| def __iter__(self): | |
| """Iterate over the entries as Alignment objects. | |
| This method SHOULD NOT be overridden by any subclass. It should be | |
| left as is, which will call the subclass implementation of __next__ | |
| to actually parse the file. | |
| """ | |
| return self | |
| def _read_header(self, stream): | |
| """Read the file header and store it in metadata.""" | |
| return | |
| def _read_next_alignment(self, stream): | |
| """Read one Alignment from the stream, and return it.""" | |
| def _close(self): | |
| try: | |
| stream = self._stream | |
| except AttributeError: | |
| return | |
| if stream is not self.source: | |
| stream.close() | |
| del self._stream | |
| class AlignmentWriter(ABC): | |
| """Base class for alignment writers. This class should be subclassed. | |
| It is intended for alignment file formats with an (optional) | |
| header, one or more alignments, and an (optional) footer. | |
| The user may call the write_file() method to write a complete | |
| file containing the alignments. | |
| Alternatively, users may call the write_header(), followed | |
| by multiple calls to format_alignment() and/or write_alignments(), | |
| followed finally by write_footer(). | |
| Subclasses may define the following class attributes: | |
| - mode - 'w' or 'wb' for text or binary files, respectively | |
| - fmt - a human-readable name for the file format. | |
| """ | |
| mode = "w" # assume text files by default | |
| fmt = None # to be defined in the subclass | |
| def __init__(self, target): | |
| """Create the writer object. | |
| Arguments: | |
| - target - output file stream, or path to output file | |
| This method MAY be overridden by any subclass. | |
| Note when subclassing: | |
| - there should be a single non-optional argument, the target. | |
| - you can add additional optional arguments. | |
| """ | |
| if target is not None: | |
| # target is None if we only use the writer to format strings. | |
| if self.mode == "w": | |
| try: | |
| target.write("") | |
| except TypeError: | |
| # target was opened in binary mode | |
| raise StreamModeError("File must be opened in text mode.") from None | |
| except AttributeError: | |
| # target is a path | |
| stream = open(target, self.mode) | |
| else: | |
| stream = target | |
| elif self.mode == "wb": | |
| try: | |
| target.write(b"") | |
| except TypeError: | |
| # target was opened in text mode | |
| raise StreamModeError( | |
| "File must be opened in binary mode." | |
| ) from None | |
| except AttributeError: | |
| # target is a path | |
| stream = open(target, self.mode) | |
| else: | |
| stream = target | |
| else: | |
| raise RuntimeError("Unknown mode '%s'" % self.mode) | |
| self.stream = stream | |
| self._target = target | |
| def write_header(self, alignments): | |
| """Write the file header to the output file.""" | |
| return | |
| ################################################## | |
| # You MUST implement this method in the subclass # | |
| # if the file format defines a file header. # | |
| ################################################## | |
| def write_footer(self): | |
| """Write the file footer to the output file.""" | |
| return | |
| ################################################## | |
| # You MUST implement this method in the subclass # | |
| # if the file format defines a file footer. # | |
| ################################################## | |
| def format_alignment(self, alignment): | |
| """Format a single alignment as a string. | |
| alignment - an Alignment object | |
| """ | |
| raise NotImplementedError("This method should be implemented") | |
| ################################################### | |
| # You MUST implement this method in the subclass. # | |
| ################################################### | |
| def write_single_alignment(self, alignments): | |
| """Write a single alignment to the output file, and return 1. | |
| alignments - A list or iterator returning Alignment objects | |
| """ | |
| count = 0 | |
| for alignment in alignments: | |
| if count == 1: | |
| raise ValueError( | |
| f"Alignment files in the {self.fmt} format can contain a single alignment only." | |
| ) | |
| line = self.format_alignment(alignment) | |
| self.stream.write(line) | |
| count += 1 | |
| return count | |
| def write_multiple_alignments(self, alignments): | |
| """Write alignments to the output file, and return the number of alignments. | |
| alignments - A list or iterator returning Alignment objects | |
| """ | |
| count = 0 | |
| for alignment in alignments: | |
| line = self.format_alignment(alignment) | |
| self.stream.write(line) | |
| count += 1 | |
| return count | |
| write_alignments = write_multiple_alignments | |
| def write_file(self, alignments): | |
| """Write a file with the alignments, and return the number of alignments. | |
| alignments - A list or iterator returning Alignment objects | |
| """ | |
| try: | |
| self.write_header(alignments) | |
| count = self.write_alignments(alignments) | |
| self.write_footer() | |
| finally: | |
| if self.stream is not self._target: | |
| self.stream.close() | |
| return count | |