"""This module provides context managers for temporarily silencing output.- Muffle: Context manager that temporarily silences output (alternative to stouputils.decorators.silent())- LogToFile: Context manager to log to a file every calls to the print functions in stouputils.print.. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/ctx_module.gif :alt: stouputils ctx examples"""# ImportsimportosimportsysfromtypingimportIO,TextIO,Callable,Anyfrom.printimportremove_colorsfrom.ioimportsuper_open# Context manager to temporarily silence output
[docs]classMuffle:""" Context manager that temporarily silences output. Alternative to stouputils.decorators.silent() Examples: >>> with Muffle(): ... print("This will not be printed") """def__init__(self,mute_stderr:bool=False)->None:self.mute_stderr:bool=mute_stderr""" Attribute remembering if stderr should be muted """self.original_stdout:TextIO=sys.stdout""" Attribute remembering original stdout """self.original_stderr:TextIO=sys.stderr""" Attribute remembering original stderr """def__enter__(self)->None:""" Enter context manager which redirects stdout and stderr to devnull """# Redirect stdout to devnullsys.stdout=open(os.devnull,"w")# Redirect stderr to devnull if neededifself.mute_stderr:sys.stderr=open(os.devnull,"w")def__exit__(self,exc_type:type[BaseException]|None,exc_val:BaseException|None,exc_tb:Any|None)->None:""" Exit context manager which restores original stdout and stderr """# Restore original stdoutsys.stdout.close()sys.stdout=self.original_stdout# Restore original stderr if neededifself.mute_stderr:sys.stderr.close()sys.stderr=self.original_stderr
# TeeMultiOutput class to duplicate output to multiple file-like objects
[docs]classTeeMultiOutput(object):""" File-like object that duplicates output to multiple file objects. Args: *files (IO[Any]): One or more file-like objects that have write and flush methods strip_colors (bool): Whether to strip ANSI color codes from output sent to non-stdout/stderr files Examples: >>> f = open("logfile.txt", "w") >>> original_stdout = sys.stdout >>> sys.stdout = TeeMultiOutput(sys.stdout, f) >>> print("Hello World") # Output goes to both console and file >>> sys.stdout = original_stdout >>> f.close() """def__init__(self,*files:IO[Any],strip_colors:bool=True)->None:self.files:tuple[IO[Any],...]=files""" File-like objects to write to """self.strip_colors:bool=strip_colors""" Whether to strip ANSI color codes from output sent to non-stdout/stderr files """
[docs]defwrite(self,obj:str)->None:""" Write the object to all files while stripping colors if needed. Args: obj (str): String to write """fori,finenumerate(self.files):ifself.strip_colorsandi!=0:f.write(remove_colors(obj))else:f.write(obj)
[docs]defflush(self)->None:""" Flush all files. """forfinself.files:f.flush()
# Add other methods that might be expected from a file-like object
[docs]defisatty(self)->bool:""" Return whether the first file is connected to a tty-like device. """returnhasattr(self.files[0],'isatty')andself.files[0].isatty()
[docs]deffileno(self)->int:""" Return the file descriptor of the first file. """returnself.files[0].fileno()
# Context manager to log to a file
[docs]classLogToFile:""" Context manager to log to a file. This context manager allows you to temporarily log output to a file while still printing normally. The file will receive log messages without ANSI color codes. Args: path (str): Path to the log file mode (str): Mode to open the file in (default: "w") encoding (str): Encoding to use for the file (default: "utf-8") tee_stdout (bool): Whether to redirect stdout to the file (default: True) tee_stderr (bool): Whether to redirect stderr to the file (default: True) Examples: .. code-block:: python > import stouputils as stp > with stp.LogToFile("output.log"): > stp.info("This will be logged to output.log and printed normally") > print("This will also be logged") """def__init__(self,path:str,mode:str="w",encoding:str="utf-8",tee_stdout:bool=True,tee_stderr:bool=True)->None:self.path:str=path""" Attribute remembering path to the log file """self.mode:str=mode""" Attribute remembering mode to open the file in """self.encoding:str=encoding""" Attribute remembering encoding to use for the file """self.tee_stdout:bool=tee_stdout""" Whether to redirect stdout to the file """self.tee_stderr:bool=tee_stderr""" Whether to redirect stderr to the file """self.file:IO[Any]=super_open(self.path,mode=self.mode,encoding=self.encoding)""" Attribute remembering opened file """self.original_stdout:TextIO=sys.stdout""" Original stdout before redirection """self.original_stderr:TextIO=sys.stderr""" Original stderr before redirection """def__enter__(self)->None:""" Enter context manager which opens the log file and redirects stdout/stderr """# Redirect stdout and stderr if requestedifself.tee_stdout:sys.stdout=TeeMultiOutput(self.original_stdout,self.file)ifself.tee_stderr:sys.stderr=TeeMultiOutput(self.original_stderr,self.file)def__exit__(self,exc_type:type[BaseException]|None,exc_val:BaseException|None,exc_tb:Any|None)->None:""" Exit context manager which closes the log file and restores stdout/stderr """# Restore original stdout and stderrifself.tee_stdout:sys.stdout=self.original_stdoutifself.tee_stderr:sys.stderr=self.original_stderr# Close fileself.file.close()
[docs]@staticmethoddefcommon(logs_folder:str,filepath:str,func:Callable[...,Any],*args:Any,**kwargs:Any)->Any:""" Common code used at the beginning of a program to launch main function Args: logs_folder (str): Folder to store logs in filepath (str): Path to the main function func (Callable[..., Any]): Main function to launch *args (tuple[Any, ...]): Arguments to pass to the main function **kwargs (dict[str, Any]): Keyword arguments to pass to the main function Returns: Any: Return value of the main function Examples: >>> if __name__ == "__main__": ... LogToFile.common(f"{ROOT}/logs", __file__, main) """# Import datetimefromdatetimeimportdatetime# Build log file pathfile_basename:str=os.path.splitext(os.path.basename(filepath))[0]date_time:str=datetime.now().strftime("%Y-%m-%d_%H-%M-%S")date_str,time_str=date_time.split("_")log_filepath:str=f"{logs_folder}/{file_basename}/{date_str}/{time_str}.log"# Launch function with arguments if anywithLogToFile(log_filepath):returnfunc(*args,**kwargs)