|
57 | 57 | "available_restarts", "available_handlers", |
58 | 58 | "restarts", "with_restarts", |
59 | 59 | "handlers", |
60 | | - "ControlError"] |
| 60 | + "ControlError", |
| 61 | + "resignal_in", "resignal"] |
61 | 62 |
|
62 | 63 | import threading |
63 | 64 | from collections import deque, namedtuple |
|
68 | 69 |
|
69 | 70 | from .collections import box, unbox |
70 | 71 | 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 |
72 | 74 |
|
73 | 75 | _stacks = threading.local() |
74 | 76 | def _ensure_stacks(): # per-thread init |
@@ -800,3 +802,98 @@ def __init__(self, value): |
800 | 802 |
|
801 | 803 | muffle = invoker("muffle") |
802 | 804 | 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