tailhead/__init__.py
def follow_path(file_path, buffering=-1, encoding=None, errors='strict'):
    """
    Similar to follow, but also looks up if inode of file is changed
    e.g. if it was re-created.

    Returned generator yields strings encoded by using encoding.
    If encoding is not specified, it defaults to locale.getpreferredencoding()

    >>> import io
    >>> import os
    >>> f = io.open('test_follow_path.txt', 'w+')
    >>> generator = follow_path('test_follow_path.txt')
    >>> _ = f.write('Line 1\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 1
    >>> _ = f.write('Line 2\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 2
    >>> _ = f.truncate(0)
    >>> _ = f.seek(0)
    >>> _ = f.write('Line 3\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 3
    >>> f.close()
    >>> os.remove('test_follow_path.txt')
    >>> f = io.open('test_follow_path.txt', 'w+')
    >>> _ = f.write('Line 4\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 4
    >>> print(next(generator))
    None
    >>> f.close()
    >>> os.remove('test_follow_path.txt')
    """
    if encoding is None:
        encoding = locale.getpreferredencoding()

    class FollowPathGenerator(object):
        def __init__(self):
            if os.path.isfile(file_path):
                self.following_file = io.open(file_path, 'rb', buffering)
                self.follow_generator = Tailer(self.following_file, end=True).follow()
                self.follow_from_end_on_open = False
            else:
                self.following_file = None
                self.follow_generator = None
                self.follow_from_end_on_open = True

        def next(self):
            while True:
                if self.follow_generator:
                    line = next(self.follow_generator)
                else:
                    line = None

                if line is None:
                    if self.follow_generator:
                        try:
                            is_file_changed = not os.path.isfile(file_path) or os.stat(file_path).st_ino != os.fstat(self.following_file.fileno()).st_ino
                        except OSError:
                            # File could be deleted between isfile and stat invocations, which will make the latter to fail.
                            is_file_changed = True

                        if is_file_changed:
                            # File was deleted or re-created.
                            self.following_file.close()
                            self.following_file = None
                            self.follow_generator = None

                    if not self.follow_generator and os.path.isfile(file_path):
                        # New file is available. Open it.
                        try:
                            self.following_file = io.open(file_path, 'rb', buffering)
                            self.follow_generator = Tailer(self.following_file, end=self.follow_from_end_on_open).follow()
                            self.follow_from_end_on_open = False  # something could be written before we noticed change of file
                        except (IOError, OSError) as e:
                            LOG.info("Unable to tail file: %s", e)
                            if self.following_file:
                                self.following_file.close()

                            self.following_file= None
                            self.follow_generator = None
                            line = None
                        else:
                            line = next(self.follow_generator)

                return line.decode(encoding, errors) if line is not None else line

        def __iter__(self):
            return self

        def __next__(self):
            return self.next()

    return FollowPathGenerator()
def follow_path(file_path, buffering=-1, encoding=None, errors='strict'):
    """
    Similar to follow, but also looks up if inode of file is changed
    e.g. if it was re-created.

    Returned generator yields strings encoded by using encoding.
    If encoding is not specified, it defaults to locale.getpreferredencoding()

    >>> import io
    >>> import os
    >>> f = io.open('test_follow_path.txt', 'w+')
    >>> generator = follow_path('test_follow_path.txt')
    >>> _ = f.write('Line 1\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 1
    >>> _ = f.write('Line 2\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 2
    >>> _ = f.truncate(0)
    >>> _ = f.seek(0)
    >>> _ = f.write('Line 3\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 3
    >>> f.close()
    >>> os.remove('test_follow_path.txt')
    >>> f = io.open('test_follow_path.txt', 'w+')
    >>> _ = f.write('Line 4\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 4
    >>> print(next(generator))
    None
    >>> f.close()
    >>> os.remove('test_follow_path.txt')
    """
    if encoding is None:
        encoding = locale.getpreferredencoding()

    class FollowPathGenerator(object):
        def __init__(self):
            if os.path.isfile(file_path):
                self.following_file = io.open(file_path, 'rb', buffering)
                self.follow_generator = Tailer(self.following_file, end=True).follow()
                self.follow_from_end_on_open = False
            else:
                self.following_file = None
                self.follow_generator = None
                self.follow_from_end_on_open = True

        def next(self):
            while True:
                if self.follow_generator:
                    line = next(self.follow_generator)
                else:
                    line = None

                if line is None:
                    if self.follow_generator:
                        try:
                            is_file_changed = not os.path.isfile(file_path) or os.stat(file_path).st_ino != os.fstat(self.following_file.fileno()).st_ino
                        except OSError:
                            # File could be deleted between isfile and stat invocations, which will make the latter to fail.
                            is_file_changed = True

                        if is_file_changed:
                            # File was deleted or re-created.
                            self.following_file.close()
                            self.following_file = None
                            self.follow_generator = None

                    if not self.follow_generator and os.path.isfile(file_path):
                        # New file is available. Open it.
                        try:
                            self.following_file = io.open(file_path, 'rb', buffering)
                            self.follow_generator = Tailer(self.following_file, end=self.follow_from_end_on_open).follow()
                            self.follow_from_end_on_open = False  # something could be written before we noticed change of file
                        except (IOError, OSError) as e:
                            LOG.info("Unable to tail file: %s", e)
                            if self.following_file:
                                self.following_file.close()

                            self.following_file= None
                            self.follow_generator = None
                            line = None
                        else:
                            line = next(self.follow_generator)

                return line.decode(encoding, errors) if line is not None else line

        def __iter__(self):
            return self

        def __next__(self):
            return self.next()

    return FollowPathGenerator()
Similar to follow, but also looks up if inode of file is changed
    e.g. if it was re-created.

    Returned generator yields strings encoded by using encoding.
    If encoding is not specified, it defaults to locale.getpreferredencoding()

    >>> import io
    >>> import os
    >>> f = io.open('test_follow_path.txt', 'w+')
    >>> generator = follow_path('test_follow_path.txt')
    >>> _ = f.write('Line 1\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 1
    >>> _ = f.write('Line 2\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 2
    >>> _ = f.truncate(0)
    >>> _ = f.seek(0)
    >>> _ = f.write('Line 3\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 3
    >>> f.close()
    >>> os.remove('test_follow_path.txt')
    >>> f = io.open('test_follow_path.txt', 'w+')
    >>> _ = f.write('Line 4\\n')
    >>> f.flush()
    >>> print(next(generator))
    Line 4
    >>> print(next(generator))
    None
    >>> f.close()
    >>> os.remove('test_follow_path.txt')