I'm writing some code to get sequences back from PDB files, wrapping the BioPython functionality.

Here's the code at the moment:

    #!/usr/bin/env python
    '''Convert a PDB file in to its representative sequence.'''
    
    import argparse
    import sys
    import os
    
    try:
        from Bio.PDB import *
        from Bio import SeqIO
    except ImportError:
        sys.stderr.write('Could not import from the Bio module. Try installing with `python -m pip install biopython`')
        sys.exit(1)
    

    def get_args():
        """Parse command line arguments"""
    
        try:
            parser = argparse.ArgumentParser(
                description='Convert a PDB file to its representative sequence(s) via BioPython.')
            parser.add_argument('-i', '--input', action='store', default=sys.stdin,
                                help='Input PDB file (defaults from STDIN for pipes).')
            parser.add_argument('-o', '--output', action='store', default=sys.stdout,
                                help='Output sequence file (defaults to STDOUT for pipes).')
            if len(sys.argv) == 1:
                parser.print_help(sys.stderr)
                exit(1)
        except:
            sys.stderr.write("An exception occurred with argument parsing. Check your provided options.")
    
        return parser.parse_args()
    
    
    def main():
        args = get_args()
        PDB, PPB = PDBParser(), PPBuilder()
        if args.input is sys.stdin:
            structure = PDB.get_structure("File from stdin", args.input)
        else:
            structure = PDB.get_structure(os.path.basename(args.input), args.input)
    
        sequences = [peptide.get_sequence() for peptide in PPB.build_peptides(structure)]
        for each in sequences:
            print(each)
    
    if __name__ == '__main__':
        main()


Now, the problem arises that I want to be able to pipe a PDB file in to the script as well as be able to pass files. Ordinarily, this would be pretty trivial via `sys.stdin`, however BioPython seemingly does something a little unusual with it's `file` objects.

If I pass `open(sys.stdin)`, it (rightly) protests that it expects a string for coercing to Unicode etc, but recieved a `file` object. Ok, says I, it should be enough to pass `sys.stdin` to the `get_structure()` constructor method, as per the line

                structure = PDB.get_structure("File from stdin", args.input)

However this results in the following error:

      File "pdb2seq.py", line 55, in <module>
        main()
      File "pdb2seq.py", line 46, in main
        structure = PDB.get_structure("File from stdin", args.input)
      File "/home/wms_joe/bin/miniconda2/lib/python2.7/site-packages/Bio/PDB/PDBParser.py", line 86, in get_structure
        self._parse(handle.readlines())
    ValueError: I/O operation on closed file

Seems obvious enough, the file is closed some how. The BioPython [source][1] for this method does the following:

      def get_structure(self, id, file): 
     69          """Return the structure. 
     70   
     71          Arguments: 
     72           - id - string, the id that will be used for the structure 
     73           - file - name of the PDB file OR an open filehandle 
     74   
     75          """ 
     76          with warnings.catch_warnings(): 
     77              if self.QUIET: 
     78                  warnings.filterwarnings("ignore", category=PDBConstructionWarning) 
     79   
     80              self.header = None 
     81              self.trailer = None 
     82              # Make a StructureBuilder instance (pass id of structure as parameter) 
     83              self.structure_builder.init_structure(id) 
     84   
     85              with as_handle(file, mode='rU') as handle: 
     86                  self._parse(handle.readlines()) 
     87   
     88              self.structure_builder.set_header(self.header) 
     89              # Return the Structure instance 
     90              structure = self.structure_builder.get_structure() 
     91   
     92          return structure 


So my guess is that BioPython's `as_handle()` method is doing something to the file handle resulting in it being closed prematurely? However, I can't see anything obvious in `as_handle()`'s [source][2] that would result in the handle being closed?

    46  def as_handle(handleish, mode='r', **kwargs): 
     47      r"""Context manager to ensure we are using a handle. 
     48   
     49      Context manager for arguments that can be passed to SeqIO and AlignIO read, write, 
     50      and parse methods: either file objects or path-like objects (strings, pathlib.Path 
     51      instances, or more generally, anything that can be handled by the builtin 'open' 
     52      function). 
     53   
     54      When given a path-like object, returns an open file handle to that path, with provided 
     55      mode, which will be closed when the manager exits. 
     56   
     57      All other inputs are returned, and are *not* closed. 
     58   
     59      Arguments: 
     60       - handleish  - Either a file handle or path-like object (anything which can be 
     61                      passed to the builtin 'open' function: str, bytes, and under 
     62                      Python >= 3.6, pathlib.Path, os.DirEntry) 
     63       - mode       - Mode to open handleish (used only if handleish is a string) 
     64       - kwargs     - Further arguments to pass to open(...) 
     65   
     66      Examples 
     67      -------- 
     68      >>> with as_handle('seqs.fasta', 'w') as fp: 
     69      ...     fp.write('>test\nACGT') 
     70      >>> fp.closed 
     71      True 
     72   
     73      >>> handle = open('seqs.fasta', 'w') 
     74      >>> with as_handle(handle) as fp: 
     75      ...     fp.write('>test\nACGT') 
     76      >>> fp.closed 
     77      False 
     78      >>> fp.close() 
     79   
     80      Note that if the mode argument includes U (for universal new lines) 
     81      this will be removed under Python 3 where is is redundant and has 
     82      been deprecated (this happens automatically in text mode). 
     83   
     84      """ 
     85      # If we're running under a version of Python that supports PEP 519, try 
     86      # to convert `handleish` to a string with `os.fspath`. 
     87      if hasattr(os, 'fspath'): 
     88          try: 
     89              handleish = os.fspath(handleish) 
     90          except TypeError: 
     91              # handleish isn't path-like, and it remains unchanged -- we'll yield it below 
     92              pass 
     93   
     94      if isinstance(handleish, basestring): 
     95          if sys.version_info[0] >= 3 and "U" in mode: 
     96              mode = mode.replace("U", "") 
     97          if 'encoding' in kwargs: 
     98              with codecs.open(handleish, mode, **kwargs) as fp: 
     99                  yield fp 
    100          else: 
    101              with open(handleish, mode, **kwargs) as fp: 
    102                  yield fp 
    103      else: 
    104          yield handleish 


## Anyone know what (if anything) I can do to make this work?

  [1]: http://biopython.org/DIST/docs/api/Bio.PDB.PDBParser%27-pysrc.html#PDBParser.get_structure
  [2]: http://biopython.org/DIST/docs/api/Bio.File-pysrc.html#as_handle
I haven't gotten to the bottom of what it is that BioPython doesn't like about `argparse`, but the solution I've come up with to allow `cat A123.pdb | python script.py` to work is the following (using Peter's suggestion):


    # Imports etc abridged...
    def get_args():    
        try:
            parser = argparse.ArgumentParser(
                description='Convert a PDB file to its representative sequence(s) via BioPython.')
            parser.add_argument('-i', '--input', action='store',
                                help='Input PDB file (defaults from STDIN for pipes).')
            parser.add_argument('-o', '--output', default=sys.stdout, action='store',
                                help='Output sequence file (defaults to STDOUT for pipes).')
        except:
            sys.stderr.write("An exception occurred with argument parsing. Check your provided options.")
            sys.exit(1)
    
        return parser.parse_args()
    
    
    def main():
        args = get_args()
        PDB, PPB = PDBParser(), PPBuilder()
        if (args.input is None) or (args.input == '-'):
            structure = PDB.get_structure("XXX", sys.stdin)
        else:
            structure = PDB.get_structure(os.path.basename(args.input), args.input)
    
        sequences = [peptide.get_sequence() for peptide in PPB.build_peptides(structure)]
        for each in sequences:
            print(each)
    
    if __name__ == '__main__':
        main()