Skip to content

Commit 3f00f5d

Browse files
committed
Split off excutil from misc; add reraise, resignal
1 parent 5e7bf7a commit 3f00f5d

File tree

10 files changed

+746
-440
lines changed

10 files changed

+746
-440
lines changed

unpythonic/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .dispatch import * # noqa: F401, F403
1818
from .dynassign import * # noqa: F401, F403
1919
from .ec import * # noqa: F401, F403
20+
from .excutil import * # noqa: F401, F403
2021
from .fix import * # noqa: F401, F403
2122
from .fold import * # noqa: F401, F403
2223
from .fploop import * # noqa: F401, F403

unpythonic/conditions.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"available_restarts", "available_handlers",
5858
"restarts", "with_restarts",
5959
"handlers",
60-
"ControlError"]
60+
"ControlError",
61+
"resignal_in", "resignal"]
6162

6263
import threading
6364
from collections import deque, namedtuple
@@ -68,7 +69,8 @@
6869

6970
from .collections import box, unbox
7071
from .arity import arity_includes, UnknownArity
71-
from .misc import namelambda, equip_with_traceback, safeissubclass
72+
from .excutil import equip_with_traceback
73+
from .misc import namelambda, safeissubclass
7274

7375
_stacks = threading.local()
7476
def _ensure_stacks(): # per-thread init
@@ -800,3 +802,98 @@ def __init__(self, value):
800802

801803
muffle = invoker("muffle")
802804
muffle.__doc__ = "Invoke the 'muffle' restart. Restart function for use with `warn`."
805+
806+
# Library to application signal type auto-conversion
807+
808+
def _resignal(mapping, condition):
809+
"""Remap an signal instance to another signal type.
810+
811+
`mapping`: dict-like, `{LibraryExc0: ApplicationExc0, ...}`
812+
813+
Each `LibraryExc` must be a signal type.
814+
815+
Each `ApplicationExc` can be a signal type or an instance.
816+
If an instance, then that exact instance is signaled as the
817+
converted signal.
818+
819+
`libraryexc`: the signal instance to convert. It is
820+
automatically chained into `ApplicationExc`.
821+
822+
This function never returns normally. If no key in the mapping
823+
matches, this delegates to the next outer handler.
824+
"""
825+
for LibraryExc, ApplicationExc in mapping.items():
826+
if isinstance(condition, LibraryExc):
827+
# TODO: Would be nice to use the same protocol as the original.
828+
# TODO: For this, we need to store that information in the signal instance.
829+
signal(ApplicationExc, cause=condition)
830+
# cancel and delegate to the next outer handler
831+
832+
def resignal_in(body, mapping):
833+
"""Remap signal types in an expression.
834+
835+
Like `unpythonic.excutil.reraise_in` (which see), but for conditions.
836+
837+
Usage::
838+
839+
resignal_in(body,
840+
{LibraryExc: ApplicationExc,
841+
...})
842+
843+
Whenever `body` signals an `exc` for which it holds that
844+
`isinstance(exc, LibraryExc)`, that signal will be transparently
845+
chained into an `ApplicationExc` signal. The automatic conversion
846+
is in effect for the dynamic extent of `body`.
847+
848+
``body`` is a thunk (0-argument function).
849+
850+
``mapping`` is dict-like, ``{input0: output0, ...}``, where each
851+
``input`` is either an exception type,
852+
or a tuple of exception types.
853+
It will be matched using `isinstance`.
854+
``output`` is an exception type or an exception
855+
instance. If an instance, then that exact
856+
instance is signaled as the converted
857+
signal.
858+
859+
Conversions are tried in the order specified; hence, just like in
860+
`with handlers`, place more specific types first.
861+
862+
See also `resignal` for a block form.
863+
"""
864+
with handlers((BaseException, partial(_resignal, mapping))):
865+
return body()
866+
867+
@contextlib.contextmanager
868+
def resignal(mapping):
869+
"""Remap signal types. Context manager.
870+
871+
Like `unpythonic.excutil.reraise` (which see), but for conditions.
872+
873+
Usage::
874+
875+
with resignal({LibraryExc: ApplicationExc, ...}):
876+
body0
877+
...
878+
879+
Whenever the body signals an `exc` for which it holds that
880+
`isinstance(exc, LibraryExc)`, that signal will be transparently
881+
chained into an `ApplicationExc` signal. The automatic conversion
882+
is in effect for the dynamic extent of the `with` block.
883+
884+
``mapping`` is dict-like, ``{input0: output0, ...}``, where each
885+
``input`` is either an exception type,
886+
or a tuple of exception types.
887+
It will be matched using `isinstance`.
888+
``output`` is an exception type or an exception
889+
instance. If an instance, then that exact
890+
instance is signaled as the converted
891+
signal.
892+
893+
Conversions are tried in the order specified; hence, just like in
894+
`with handlers`, place more specific types first.
895+
896+
See also `resignal_in` for an expression form.
897+
"""
898+
with handlers((BaseException, partial(_resignal, mapping))):
899+
yield

0 commit comments

Comments
 (0)