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 | |