""" compatibility module to break an import cycle, do not directly use this Access this functionality from :py:module:`snakeoil.osutils` instead """ __all__ = ( "mmap_and_close", "readlines_iter", "native_readlines", "native_readfile", ) import errno import itertools import mmap import os def mmap_and_close(fd, *args, **kwargs): """ see :py:obj:`mmap.mmap`; basically this maps, then closes, to ensure the fd doesn't bleed out. """ try: return mmap.mmap(fd, *args, **kwargs) finally: try: os.close(fd) except EnvironmentError: pass class readlines_iter: __slots__ = ("iterable", "mtime", "source") def __init__(self, iterable, mtime, close=True, source=None): if source is None: source = iterable self.source = source if close: iterable = itertools.chain(iterable, self._close_on_stop(source)) self.iterable = iterable self.mtime = mtime @staticmethod def _close_on_stop(source): # we explicitly write this to force this method to be # a generator; we intend to return nothing, but close # the file on the way out. # Note that in addition, this is a staticmethod; if it's an instance # method it results in cyclic reference graph for this object. # End result, files hang around until a full gc collection spots the # cycle and reaps the cyclic readlines_iter chunk. for _ in (): yield None source.close() def close(self): if hasattr(self.source, "close"): self.source.close() def __iter__(self): return self.iterable def native_readlines( mode, mypath, strip_whitespace=True, swallow_missing=False, none_on_missing=False, encoding=None, ): """Read a file, yielding each line. :param mypath: fs path for the file to read :param strip_whitespace: strip any leading or trailing whitespace including newline? :param swallow_missing: throw an IOError if missing, or swallow it? :param none_on_missing: if the file is missing, return None, else if the file is missing return an empty iterable """ handle = iterable = None try: handle = open(mypath, mode, encoding=encoding) except IOError as ie: if not swallow_missing or ie.errno not in (errno.ENOTDIR, errno.ENOENT): raise if none_on_missing: return None return readlines_iter(iter([]), None, close=False) mtime = os.fstat(handle.fileno()).st_mtime if not iterable: iterable = iter(handle) if not strip_whitespace: return readlines_iter(iterable, mtime) return readlines_iter(_strip_whitespace_filter(iterable), mtime, source=handle) def _strip_whitespace_filter(iterable): for line in iterable: yield line.strip() def native_readfile(mode, mypath, none_on_missing=False, encoding=None): """Read a file, returning the contents. :param mypath: fs path for the file to read :param none_on_missing: whether to return None if the file is missing, else through the exception """ f = None try: try: f = open(mypath, mode, encoding=encoding) return f.read() except IOError as oe: if none_on_missing and oe.errno in (errno.ENOENT, errno.ENOTDIR): return None raise finally: if f is not None: f.close()