Source code for selfisys.utils.low_level

#!/usr/bin/env python3
# ----------------------------------------------------------------------
# Copyright (C) 2024 Tristan Hoellinger
# Distributed under the GNU General Public License v3.0 (GPLv3).
# See the LICENSE file in the root directory for details.
# SPDX-License-Identifier: GPL-3.0-or-later
# ----------------------------------------------------------------------

__author__ = "Tristan Hoellinger"
__version__ = "0.1.0"
__date__ = "2024"
__license__ = "GPLv3"

"""
Tools to deal with low-level operations such as redirecting stdout from
C code.
"""

from contextlib import contextmanager
import platform
import ctypes
import io
import os, sys
import tempfile

libc = ctypes.CDLL(None)
if platform.system() == "Darwin":  # macOS
    stdout_symbol = "__stdoutp"
    stderr_symbol = "__stderrp"
else:
    stdout_symbol = "stdout"
    stderr_symbol = "stderr"
c_stdout = ctypes.c_void_p.in_dll(libc, stdout_symbol)
c_stderr = ctypes.c_void_p.in_dll(libc, stderr_symbol)


# Taken from:
# https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/
[docs] @contextmanager def stdout_redirector(stream): """A context manager that redirects stdout to the given stream. For instance, this can be used to redirect C code stdout to None (to avoid cluttering the log, e.g., when using tqdm). Args: stream (file-like object): The stream to which stdout should be redirected. Example: >>> with stdout_redirector(stream): >>> print("Hello world!") # Will be printed to stream >>> # instead of stdout. """ # The original fd stdout points to. Usually 1 on POSIX systems. original_stdout_fd = sys.stdout.fileno() def _redirect_stdout(to_fd): """Redirect stdout to the given file descriptor.""" # Flush the C-level buffer stdout libc.fflush(c_stdout) # Flush and close sys.stdout - also closes the file descriptor (fd) sys.stdout.close() # Make original_stdout_fd point to the same file as to_fd os.dup2(to_fd, original_stdout_fd) # Create a new sys.stdout that points to the redirected fd sys.stdout = io.TextIOWrapper(os.fdopen(original_stdout_fd, "wb")) # Save a copy of the original stdout fd in saved_stdout_fd saved_stdout_fd = os.dup(original_stdout_fd) try: # Create a temporary file and redirect stdout to it tfile = tempfile.TemporaryFile(mode="w+b") _redirect_stdout(tfile.fileno()) # Yield to caller, then redirect stdout back to the saved fd yield _redirect_stdout(saved_stdout_fd) # Copy contents of temporary file to the given stream tfile.flush() tfile.seek(0, io.SEEK_SET) stream.write(tfile.read()) finally: tfile.close() os.close(saved_stdout_fd)
# Adapted from: # https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/
[docs] @contextmanager def stderr_redirector(stream): """A context manager that redirects stderr to the given stream. For instance, this can be used to redirect C code stderr to None (to avoid cluttering the log, e.g., when using tqdm). Use with caution. Args: stream (file-like object): The stream to which stdout should be redirected. """ # The original fd stdout points to. Usually 1 on POSIX systems. original_stderr_fd = sys.stderr.fileno() def _redirect_stderr(to_fd): """Redirect stderr to the given file descriptor.""" # Flush the C-level buffer stderr libc.fflush(c_stderr) # Flush and close sys.stderr - also closes the file descriptor (fd) sys.stderr.close() # Make original_stderr_fd point to the same file as to_fd os.dup2(to_fd, original_stderr_fd) # Create a new sys.stderr that points to the redirected fd sys.stderr = io.TextIOWrapper(os.fdopen(original_stderr_fd, "wb")) # Save a copy of the original stdout fd in saved_stdout_fd saved_stderr_fd = os.dup(original_stderr_fd) try: # Create a temporary file and redirect stdout to it tfile = tempfile.TemporaryFile(mode="w+b") _redirect_stderr(tfile.fileno()) # Yield to caller, then redirect stdout back to the saved fd yield _redirect_stderr(saved_stderr_fd) # Copy contents of temporary file to the given stream tfile.flush() tfile.seek(0, io.SEEK_SET) stream.write(tfile.read()) finally: tfile.close() os.close(saved_stderr_fd)