Source code for coexist.utilities
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File : utilities.py
# License: GNU v3.0
# Author : Andrei Leonard Nicusan <a.l.nicusan@bham.ac.uk>
# Date : 04.08.2021
import sys
import signal
from textwrap import indent
class UniversalSet:
def __contains__(self, x):
return True
universal_set = UniversalSet()
[docs]def autorepr(_c = None, *, short = {}, hide = {}):
'''Automatically create a ``__repr__`` method for pretty-printing class
attributes; they are discovered at runtime following some rules.
1. Attribute names do not start with underscores and are not callable.
2. The attributes given in ``short`` (set[str] | bool) are printed up to
80 characters. If ``short == True``, then all attributes are shortened.
3. The attributes given in ``hide`` (set[str]) are skipped.
4. If the attribute representation is multiline (i.e. has newlines) then it
is printed on a separate line and indented with 2 spaces.
Examples
--------
>>> @autorepr
>>> class SomeClass:
>>> def __init__(self):
>>> self.x = "spam"
>>> self.y = "eggs"
>>>
>>> print(SomeClass())
SomeClass
---------
x = spam
y = eggs
>>> @autorepr(hide = {"x"})
>>> class SomeClass:
>>> def __init__(self):
>>> self.x = "spam"
>>> self.y = "eggs"
>>>
>>> print(SomeClass())
SomeClass
---------
y = eggs
'''
def __repr__(self):
# Skip those attributes from the representation
if hasattr(self, "_repr_hide"):
_repr_hide = self._repr_hide
else:
_repr_hide = set()
# Shorten those attributes' representation
if hasattr(self, "_repr_short"):
_repr_short = self._repr_short
else:
_repr_short = set()
# Return pretty string representation of an arbitrary object
docs = []
for att in dir(self):
if not att.startswith("_") and att not in _repr_hide:
memb = getattr(self, att)
if not callable(memb):
# If memb_str is multiline, indent it
memb_str = str(memb)
if att not in _repr_short and "\n" in memb_str:
memb_str = "\n" + indent(memb_str, " ")
docs.append(f"{att} = {memb_str}")
if att in _repr_short and len(docs[-1]) > 80:
docs[-1] = docs[-1].replace("\n", "\\n")[:67] + "..."
name = self.__class__.__name__
underline = "-" * len(name)
return f"{name}\n{underline}\n" + "\n".join(docs)
def setrepr(c):
# Attach the attribute names to shorten / hide as class attributes
if isinstance(short, bool) and short is True:
c._repr_short = universal_set
elif len(short):
c._repr_short = set(short)
if len(hide):
c._repr_hide = set(hide)
c.__repr__ = __repr__
return c
if _c is None:
return setrepr
return setrepr(_c)
def interrupt_handler(signum, stackframe):
li = "\n" + "*" * 80 + "\n"
print(
f"{li}Caught signal {signum} - will kill subprocesses and abort!{li}",
flush = True, file = sys.stderr,
)
raise KeyboardInterrupt
[docs]class SignalHandlerKI:
'''Handle typical OS termination signals by raising a ``KeyboardInterrupt``
exception.
If a signal is not found on a given platform (e.g. SIGBREAK only exists on
Windows) it is simply skipped.
'''
[docs] def __init__(
self,
signals = [
"SIGINT",
"SIGTERM",
"SIGBREAK",
"SIGABRT",
],
):
self.signals = signals
self.previous_handlers = {}
[docs] def set(self):
'''Set the signals' handlers. Save the previous handlers.
'''
# The signal number may not exist (AttributeError) or be wrong
# (ValueError) on some platforms, so catch possible exceptions and
# simply ignore these signals
for sig in self.signals:
try:
s = getattr(signal, sig) # AttributeError
self.previous_handlers[sig] = signal.getsignal(s)
signal.signal(s, interrupt_handler) # Key|ValueError
except (AttributeError, KeyError, ValueError):
pass
[docs] def unset(self):
'''Unset the signals' handlers. Return to previous handlers.
'''
# The signal number may not exist (AttributeError) or be wrong
# (ValueError) on some platforms, so catch possible exceptions and
# simply ignore these signals
for sig in self.signals:
try:
s = getattr(signal, sig) # AttributeError
signal.signal(s, self.previous_handlers[sig]) # Key|ValueError
except (AttributeError, KeyError, ValueError):
pass