Hi Lucas (2022.03.26_17:21:07_-0400) > > self = FileType('wb'), string = '-' > > > > def __call__(self, string): > > # the special argument "-" means sys.std{in,out} > > if string == '-': > > if 'r' in self._mode: > > return _sys.stdin.buffer if 'b' in self._mode else > > _sys.stdin > > elif any(c in self._mode for c in 'wax'): > > > return _sys.stdout.buffer if 'b' in self._mode else > > > _sys.stdout > > E AttributeError: '_io.StringIO' object has no attribute > > 'buffer'
I assume the big relevant change here is https://bugs.python.org/issue14156 being fixed I experimented a little and got to this patch, but it isn't complete yet. SR -- Stefano Rivera http://tumbleweed.org.za/
--- a/tests/khmer_tst_utils.py +++ b/tests/khmer_tst_utils.py @@ -44,18 +44,13 @@ import sys import traceback import subprocess -from io import open # pylint: disable=redefined-builtin +from io import BufferedWriter, BytesIO, StringIO, TextIOWrapper from hashlib import md5 from khmer import reverse_complement as revcomp import pytest -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - def _equals_rc(query, match): return (query == match) or (revcomp(query) == match) @@ -151,6 +146,25 @@ return -1 +class StdIOBuffer(TextIOWrapper): + '''Replacement for writable io.StringIO that behaves more like real file + Unlike StringIO, provides a buffer attribute that holds the underlying + binary data, allowing it to replace sys.stdout/sys.stderr in more + contexts. + ''' + + name = 'StdIOBuffer' + + def __init__(self, initial_value='', newline='\n'): + initial_value = initial_value.encode('utf-8') + super().__init__(BufferedWriter(BytesIO(initial_value)), + 'utf-8', newline=newline) + + def getvalue(self): + self.flush() + return self.buffer.raw.getvalue().decode('utf-8') + + def runscript(scriptname, args, in_directory=None, fail_ok=False, sandbox=False): """Run a Python script using exec(). @@ -172,9 +186,8 @@ sys.argv = sysargs oldout, olderr = sys.stdout, sys.stderr - sys.stdout = StringIO() - sys.stdout.name = "StringIO" - sys.stderr = StringIO() + sys.stdout = StdIOBuffer() + sys.stderr = StdIOBuffer() if in_directory: os.chdir(in_directory) --- a/khmer/khmer_args.py +++ b/khmer/khmer_args.py @@ -158,14 +158,16 @@ # Temporary fix to argparse FileType which ignores the # binary mode flag. Upstream bug tracked in https://bugs.python.org/issue14156 +# Fixed in 3.9.12 and 3.10.3. # pylint: disable=too-few-public-methods,missing-docstring class FileType(argparse.FileType): def __call__(self, fname): # detect if stdout is being faked (StringIO during unit tests) in # which case we do not have to do anything if (fname == '-' and - sys.version_info.major == 3 and - not isinstance(sys.stdout, StringIO)): + not isinstance(sys.stdout, StringIO) and + (sys.version_info < (3, 9, 12) or + (3, 10) < sys.version_info < (3, 10, 3))): if 'r' in self._mode: fname = sys.stdin.fileno() elif 'w' in self._mode: --- a/khmer/kfile.py +++ b/khmer/kfile.py @@ -209,6 +209,8 @@ """Take in a file object and checks to see if it's a block or fifo.""" if fthing is sys.stdout or fthing is sys.stdin: return True + elif not hasattr(fthing, 'name'): + return True else: mode = os.stat(fthing.name).st_mode return S_ISBLK(mode) or S_ISCHR(mode) --- a/scripts/trim-low-abund.py +++ b/scripts/trim-low-abund.py @@ -512,7 +512,7 @@ if args.output is None: log_info('output in *.abundtrim') - elif args.output.name == 1: + elif not hasattr(args.output, 'name') or args.output.name == 1: log_info('output streamed to stdout') elif args.output.name: log_info('output in {}'.format(args.output.name))