diff --git a/docs/exception_handling/index.rst b/docs/exception_handling/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..0ea44485de00ec71f520c6b1753d9fff1abf5a21 --- /dev/null +++ b/docs/exception_handling/index.rst @@ -0,0 +1,13 @@ +Exception handling +================== + +Satella provides a rich functionality to register exception hooks. + +Writing your own exception handlers +----------------------------------- + +To write your own exception handlers, subclass the following class: + +.. autoclass:: satella.exception_handling.BaseExceptionHandler + :members: + diff --git a/docs/index.rst b/docs/index.rst index bea568620e6a1a322e7845644b4fb1734e2aef5f..30e19e9e83f6480dc5c1f426a4f2c042151e6e0e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Welcome to satella's documentation! coding/concurrent instrumentation/traceback instrumentation/metrics + exception_handling/index json posix recipes diff --git a/satella/posix/__init__.py b/satella/posix/__init__.py index 183ac7e0997ed5d48372fbcedfa97fae927fc77d..88c5ed0588ee0f66d3ec4a5dd168f1895194155c 100644 --- a/satella/posix/__init__.py +++ b/satella/posix/__init__.py @@ -1,17 +1,13 @@ -# coding=UTF-8 """ -UNIX things +POSIX things """ -from __future__ import print_function, absolute_import, division -import logging import os from .daemon import daemonize from .pidlock import AcquirePIDLock from .signals import hang_until_sig -logger = logging.getLogger(__name__) __all__ = [ 'daemonize', @@ -27,6 +23,7 @@ def is_running_as_root(): Is this process running as root? Checks whether EUID is 0 + :return: bool """ return os.geteuid() == 0 diff --git a/satella/posix/daemon.py b/satella/posix/daemon.py index 73615c4222d8d213366489cc279dfba833a7ab74..69d81a023390d2515c31d83597e59e23bc014588 100644 --- a/satella/posix/daemon.py +++ b/satella/posix/daemon.py @@ -20,6 +20,9 @@ logger = logging.getLogger(__name__) DEVNULL = '/dev/null' +__all__ = ['daemonize'] + + def daemonize(exit_via: tp.Callable = sys.exit, redirect_std_to_devnull: bool = True, uid: tp.Optional[int] = None, @@ -58,10 +61,10 @@ def daemonize(exit_via: tp.Callable = sys.exit, _parse_ug(gid, grp, 'gr_gid', os.setegid) -def _parse_ug(no, module, fieldname, osfun): +def _parse_ug(no, module, field_name, osfun): if no is not None: if isinstance(no, str): - no = getattr(module.getpwnam(no), fieldname) + no = getattr(module.getpwnam(no), field_name) osfun(no) diff --git a/satella/posix/pidlock.py b/satella/posix/pidlock.py index ef920b077dab4a77c1d4ae0526269d0f8242ac7b..30ded11ad61c59c27fedd3dba35d5229c6a2fefe 100644 --- a/satella/posix/pidlock.py +++ b/satella/posix/pidlock.py @@ -24,13 +24,17 @@ class AcquirePIDLock: Usage: - with AcquirePIDLock('myservice.pid'): + >>> with AcquirePIDLock('myservice.pid'): + >>> ... rest of code .. - .. rest of code .. + Or alternatively - Exiting the context manager deletes the file. + >>> pid_lock = AcquirePIDLock('myservice.pid') + >>> pid_lock.acquire() + >>> ... + >>> pid_lock.release() - The constructor doesn't throw, __enter__ does, one of: + The constructor doesn't throw, __enter__ or acquire() does, one of: * AcquirePIDLock.FailedToAcquire - base class for errors. Thrown if can't read the file * AcquirePIDLock.LockIsHeld - lock is already held. This has two attributes - pid (int), the PID of holder, @@ -55,8 +59,18 @@ class AcquirePIDLock: self.fileno = None - def _acquire(self): - """The mechanical process of acquisition""" + def release(self): + if self.fileno is not None: + os.close(self.fileno) + os.unlink(self.path) + + def acquire(self): + """ + Acquire the PID lock + + :raises LockIsHeld: if lock if held + :raises FailedToAcquire: if for example a directory exists in that place + """ try: self.fileno = os.open(self.path, os.O_CREAT | os.O_EXCL) except (IOError, OSError): @@ -69,7 +83,7 @@ class AcquirePIDLock: 'PID file found but doesn''t have an int, skipping') return except IOError as e: - raise FailedToAcquire() + raise FailedToAcquire(repr(e)) # Is this process alive? try: @@ -81,17 +95,15 @@ class AcquirePIDLock: def __enter__(self): try: - self._acquire() + self.acquire() except LockIsHeld as e: if self.delete_on_dead and (not e.is_alive): os.unlink(self.path) - self._acquire() + self.acquire() else: raise self.success = True def __exit__(self, exc_type, exc_val, exc_tb): - if self.fileno is not None: - os.close(self.fileno) - os.unlink(self.path) + self.release() diff --git a/satella/posix/signals.py b/satella/posix/signals.py index 9c3b1959c695b572b61e350b9e349f8e3f4b6c56..ad51aa5ab2b9ada86eee314c6d6b98ce140719d3 100644 --- a/satella/posix/signals.py +++ b/satella/posix/signals.py @@ -14,10 +14,14 @@ def __sighandler(a, b): end = True -def hang_until_sig(extra_signals: tp.Optional[tp.List] = None): - """Will hang until this process receives SIGTERM or SIGINT. +def hang_until_sig(extra_signals: tp.Optional[tp.List[int]] = None): + """ + Will hang until this process receives SIGTERM or SIGINT. If you pass extra signal IDs (signal.SIG*) with extra_signals, - then also on those signals this call will release.""" + then also on those signals this call will release. + + :param extra_signals: a list of extra signals to listen to + """ extra_signals = extra_signals or [] global end