Rework `collapse_eg()` to NOT use `except*`..
Since it turns out the semantics are basically inverse of normal `except` (particularly for re-raising) which is hard to get right, and bc it's a lot easier to just delegate to what `trio` already has behind the `strict_exception_groups=False` setting, Bp I added a rant here which will get removed shortly likely, but i think going forward recommending against use of `except*` is prudent for anything low level enough in the runtime (like trying to filter begs). Dirty deats, - copy `trio._core._run.collapse_exception_group()` to here with only a slight mod to remove the notes check and tb concatting for the collapse case. - rename `maybe_collapse_eg()` - > `get_collapsed_eg()` and delegate it directly to the former `trio` fn; return `None` when it returns the same beg without collapse. - simplify our own `collapse_eg()` to either raise the collapsed `exc` or original `beg`.to_asyncio_eoc_signal
							parent
							
								
									ccc3b1fce1
								
							
						
					
					
						commit
						72c4a9d20b
					
				|  | @ -31,7 +31,7 @@ from ._broadcast import ( | |||
| ) | ||||
| from ._beg import ( | ||||
|     collapse_eg as collapse_eg, | ||||
|     maybe_collapse_eg as maybe_collapse_eg, | ||||
|     get_collapsed_eg as get_collapsed_eg, | ||||
|     is_multi_cancelled as is_multi_cancelled, | ||||
| ) | ||||
| from ._taskc import ( | ||||
|  |  | |||
|  | @ -15,8 +15,9 @@ | |||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| ''' | ||||
| `BaseExceptionGroup` related utils and helpers pertaining to | ||||
| first-class-`trio` from a historical perspective B) | ||||
| `BaseExceptionGroup` utils and helpers pertaining to | ||||
| first-class-`trio` from a "historical" perspective, like "loose | ||||
| exception group" task-nurseries. | ||||
| 
 | ||||
| ''' | ||||
| from contextlib import ( | ||||
|  | @ -24,28 +25,83 @@ from contextlib import ( | |||
| ) | ||||
| from typing import ( | ||||
|     Literal, | ||||
|     Type, | ||||
| ) | ||||
| 
 | ||||
| import trio | ||||
| # from trio._core._concat_tb import ( | ||||
| #     concat_tb, | ||||
| # ) | ||||
| 
 | ||||
| 
 | ||||
| def maybe_collapse_eg( | ||||
| # XXX NOTE | ||||
| # taken verbatim from `trio._core._run` except, | ||||
| # - remove the NONSTRICT_EXCEPTIONGROUP_NOTE deprecation-note | ||||
| #   guard-check; we know we want an explicit collapse. | ||||
| # - mask out tb rewriting in collapse case, i don't think it really | ||||
| #   matters? | ||||
| # | ||||
| def collapse_exception_group( | ||||
|     excgroup: BaseExceptionGroup[BaseException], | ||||
| ) -> BaseException: | ||||
|     """Recursively collapse any single-exception groups into that single contained | ||||
|     exception. | ||||
| 
 | ||||
|     """ | ||||
|     exceptions = list(excgroup.exceptions) | ||||
|     modified = False | ||||
|     for i, exc in enumerate(exceptions): | ||||
|         if isinstance(exc, BaseExceptionGroup): | ||||
|             new_exc = collapse_exception_group(exc) | ||||
|             if new_exc is not exc: | ||||
|                 modified = True | ||||
|                 exceptions[i] = new_exc | ||||
| 
 | ||||
|     if ( | ||||
|         len(exceptions) == 1 | ||||
|         and isinstance(excgroup, BaseExceptionGroup) | ||||
| 
 | ||||
|         # XXX trio's loose-setting condition.. | ||||
|         # and NONSTRICT_EXCEPTIONGROUP_NOTE in getattr(excgroup, "__notes__", ()) | ||||
|     ): | ||||
|         # exceptions[0].__traceback__ = concat_tb( | ||||
|         #     excgroup.__traceback__, | ||||
|         #     exceptions[0].__traceback__, | ||||
|         # ) | ||||
|         return exceptions[0] | ||||
|     elif modified: | ||||
|         return excgroup.derive(exceptions) | ||||
|     else: | ||||
|         return excgroup | ||||
| 
 | ||||
| 
 | ||||
| def get_collapsed_eg( | ||||
|     beg: BaseExceptionGroup, | ||||
| ) -> BaseException|bool: | ||||
| 
 | ||||
|     bp: bool = False, | ||||
| ) -> BaseException|None: | ||||
|     ''' | ||||
|     If the input beg can collapse to a single non-eg sub-exception, | ||||
|     return it instead. | ||||
|     If the input beg can collapse to a single sub-exception which is | ||||
|     itself **not** an eg, return it. | ||||
| 
 | ||||
|     ''' | ||||
|     if len(excs := beg.exceptions) == 1: | ||||
|         return excs[0] | ||||
|     maybe_exc = collapse_exception_group(beg) | ||||
|     if maybe_exc is beg: | ||||
|         return None | ||||
| 
 | ||||
|     return maybe_exc | ||||
| 
 | ||||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| @acm | ||||
| async def collapse_eg( | ||||
|     hide_tb: bool = True, | ||||
| 
 | ||||
|     ignore: set[Type[BaseException]] = { | ||||
|         # trio.Cancelled, | ||||
|     }, | ||||
|     add_notes: bool = True, | ||||
|     bp: bool = False, | ||||
| ): | ||||
|     ''' | ||||
|     If `BaseExceptionGroup` raised in the body scope is | ||||
|  | @ -57,16 +113,64 @@ async def collapse_eg( | |||
|     __tracebackhide__: bool = hide_tb | ||||
|     try: | ||||
|         yield | ||||
|     except* BaseException as beg: | ||||
|     except BaseExceptionGroup as _beg: | ||||
|         beg = _beg | ||||
| 
 | ||||
|     # TODO, remove this rant.. | ||||
|     # | ||||
|     # except* BaseException as beg: | ||||
|     # ^XXX WOW.. good job cpython. ^ | ||||
|     # like, never ever EVER use this!! XD | ||||
|     # | ||||
|     # turns out rasing from an `except*`-block has the opposite | ||||
|     # behaviour of normal `except` and further can *never* be used to | ||||
|     # get the equiv of, | ||||
|     # `trio.open_nursery(strict_exception_groups=False)` | ||||
|     # | ||||
|     # ------ docs ------ | ||||
|     # https://docs.python.org/3/reference/compound_stmts.html#except-star | ||||
|     # | ||||
|     # > Any remaining exceptions that were not handled by any | ||||
|     # > except* clause are re-raised at the end, along with all | ||||
|     # > exceptions that were raised from within the except* | ||||
|     # > clauses. If this list contains more than one exception to | ||||
|     # > reraise, they are combined into an exception group. | ||||
|         if bp: | ||||
|             from tractor.devx import pause | ||||
|             await pause(shield=True) | ||||
| 
 | ||||
|         if ( | ||||
|             exc := maybe_collapse_eg(beg) | ||||
|             (exc := get_collapsed_eg(beg)) | ||||
|             and | ||||
|             type(exc) not in ignore | ||||
|         ): | ||||
|             if cause := exc.__cause__: | ||||
|                 raise exc from cause | ||||
| 
 | ||||
|             # TODO? report number of nested groups it was collapsed | ||||
|             # *from*? | ||||
|             if add_notes: | ||||
|                 from_group_note: str = ( | ||||
|                     '( ^^^ this exc was collapsed from a group ^^^ )\n' | ||||
|                 ) | ||||
|                 if ( | ||||
|                     from_group_note | ||||
|                     not in | ||||
|                     getattr(exc, "__notes__", ()) | ||||
|                 ): | ||||
|                     exc.add_note(from_group_note) | ||||
| 
 | ||||
|             raise exc | ||||
| 
 | ||||
|         raise beg | ||||
|             # ?TODO? not needed right? | ||||
|             # if cause := exc.__cause__: | ||||
|             #     raise exc# from cause | ||||
|             # else: | ||||
|             #     # raise exc from beg | ||||
|             #     # suppress "during handling of <the beg>" | ||||
|             #     # output in tb/console. | ||||
|             #     raise exc from None | ||||
| 
 | ||||
|         # keep original | ||||
|         raise # beg | ||||
| 
 | ||||
| 
 | ||||
| def is_multi_cancelled( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue