From e520bd88077014e69d4b54a9303498b18974adca Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Sun, 6 Aug 2023 01:22:47 -0500 Subject: [PATCH 01/13] WIP: Add typing for `_core/_multierror.py` --- trio/_core/_multierror.py | 168 ++++++++++++++++++++++++++++++++++---- 1 file changed, 150 insertions(+), 18 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 3c6ebb789f..1791883aee 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -2,7 +2,8 @@ import sys import warnings -from typing import TYPE_CHECKING +from collections.abc import Callable, Iterable +from typing import TYPE_CHECKING, TypeVar import attr @@ -15,12 +16,16 @@ if TYPE_CHECKING: from types import TracebackType + + from typing_extensions import Self ################################################################ # MultiError ################################################################ -def _filter_impl(handler, root_exc): +def _filter_impl( + handler: Callable[[BaseException], BaseException], root_exc: BaseException +) -> BaseException | None: # We have a tree of MultiError's, like: # # MultiError([ @@ -79,7 +84,9 @@ def _filter_impl(handler, root_exc): # Filters a subtree, ignoring tracebacks, while keeping a record of # which MultiErrors were preserved unchanged - def filter_tree(exc, preserved): + def filter_tree( + exc: MultiError | BaseException, preserved: set[int] + ) -> MultiError | BaseException | None: if isinstance(exc, MultiError): new_exceptions = [] changed = False @@ -103,7 +110,9 @@ def filter_tree(exc, preserved): new_exc.__context__ = exc return new_exc - def push_tb_down(tb, exc, preserved): + def push_tb_down( + tb: TracebackType | None, exc: BaseException, preserved: set[int] + ) -> None: if id(exc) in preserved: return new_tb = concat_tb(tb, exc.__traceback__) @@ -114,7 +123,7 @@ def push_tb_down(tb, exc, preserved): else: exc.__traceback__ = new_tb - preserved = set() + preserved: set[int] = set() new_root_exc = filter_tree(root_exc, preserved) push_tb_down(None, root_exc, preserved) # Delete the local functions to avoid a reference cycle (see @@ -130,9 +139,9 @@ def push_tb_down(tb, exc, preserved): # frame show up in the traceback; otherwise, we leave no trace.) @attr.s(frozen=True) class MultiErrorCatcher: - _handler = attr.ib() + _handler: Callable[[BaseException], BaseException] = attr.ib() - def __enter__(self): + def __enter__(self) -> None: pass def __exit__( @@ -167,6 +176,51 @@ def __exit__( return False +# types: type-arg error: Missing type parameters for generic type "BaseExceptionGroup" +# types: note: Another file has errors: trio/_timeouts.py +# types: note: Another file has errors: trio/_core/_entry_queue.py +# types: note: Another file has errors: trio/_signals.py +# types: note: Another file has errors: trio/_util.py +# types: note: Another file has errors: trio/_subprocess_platform/waitid.py +# types: note: Another file has errors: trio/testing/__init__.py +# types: note: Another file has errors: trio/_core/_thread_cache.py +# types: note: Another file has errors: trio/_channel.py +# types: note: Another file has errors: trio/testing/_network.py +# types: note: Another file has errors: trio/_core/_wakeup_socketpair.py +# types: note: Another file has errors: trio/_subprocess.py +# types: note: Another file has errors: trio/_core/_unbounded_queue.py +# types: note: Another file has errors: trio/_path.py +# types: note: Another file has errors: trio/testing/_memory_streams.py +# types: note: Another file has errors: trio/_highlevel_open_unix_stream.py +# types: note: Another file has errors: trio/testing/_checkpoints.py +# types: note: Another file has errors: trio/_core/_traps.py +# types: note: Another file has errors: trio/_highlevel_serve_listeners.py +# types: note: Another file has errors: trio/_core/_generated_instrumentation.py +# types: note: Another file has errors: trio/_socket.py +# types: note: Another file has errors: trio/_core/_io_common.py +# types: note: Another file has errors: trio/lowlevel.py +# types: note: Another file has errors: trio/_core/_ki.py +# types: note: Another file has errors: trio/_core/_asyncgens.py +# types: note: Another file has errors: trio/_highlevel_ssl_helpers.py +# types: note: Another file has errors: trio/_ssl.py +# types: note: Another file has errors: trio/testing/_trio_test.py +# types: note: Another file has errors: trio/_core/_parking_lot.py +# types: note: Another file has errors: trio/_highlevel_open_tcp_listeners.py +# types: note: Another file has errors: trio/testing/_check_streams.py +# types: note: Another file has errors: trio/_highlevel_open_tcp_stream.py +# types: note: Another file has errors: trio/_core/_generated_io_epoll.py +# types: note: Another file has errors: trio/_core/_mock_clock.py +# types: note: Another file has errors: trio/_sync.py +# types: note: Another file has errors: trio/_threads.py +# types: note: Another file has errors: trio/_dtls.py +# types: note: Another file has errors: trio/_core/_run.py +# types: note: Another file has errors: trio/__init__.py +# types: note: Another file has errors: trio/_core/_generated_run.py +# types: note: Another file has errors: trio/_highlevel_socket.py +# types: note: Another file has errors: trio/_unix_pipes.py +# types: note: Another file has errors: trio/_core/_io_epoll.py +# types: note: Another file has errors: trio/testing/_sequencer.py +# types: type-arg error: Missing type parameters for generic type "BaseExceptionGroup" class MultiError(BaseExceptionGroup): """An exception that contains other exceptions; also known as an "inception". @@ -190,7 +244,9 @@ class MultiError(BaseExceptionGroup): """ - def __init__(self, exceptions, *, _collapse=True): + def __init__( + self, exceptions: list[BaseException], *, _collapse: bool = True + ) -> None: self.collapse = _collapse # Avoid double initialization when _collapse is True and exceptions[0] returned @@ -201,7 +257,9 @@ def __init__(self, exceptions, *, _collapse=True): super().__init__("multiple tasks failed", exceptions) - def __new__(cls, exceptions, *, _collapse=True): + def __new__( + cls, exceptions: Iterable[BaseException], *, _collapse: bool = True + ) -> MultiError | Self: exceptions = list(exceptions) for exc in exceptions: if not isinstance(exc, BaseException): @@ -210,7 +268,9 @@ def __new__(cls, exceptions, *, _collapse=True): # If this lone object happens to itself be a MultiError, then # Python will implicitly call our __init__ on it again. See # special handling in __init__. - return exceptions[0] + single = exceptions[0] + assert isinstance(single, MultiError) + return single else: # The base class __new__() implicitly invokes our __init__, which # is what we want. @@ -223,6 +283,58 @@ def __new__(cls, exceptions, *, _collapse=True): return super().__new__(cls, "multiple tasks failed", exceptions) + # types: Error running mypy: Daemon crashed! + # types: Traceback (most recent call last): + # types: File "mypy/suggestions.py", line 296, in restore_after + # types: File "mypy/suggestions.py", line 267, in suggest + # types: File "mypy/suggestions.py", line 502, in get_suggestion + # types: File "mypy/suggestions.py", line 444, in find_best + # types: File "mypy/suggestions.py", line 675, in try_type + # types: File "mypy/server/update.py", line 314, in trigger + # types: File "mypy/server/update.py", line 881, in propagate_changes_using_dependencies + # types: File "mypy/server/update.py", line 1010, in reprocess_nodes + # types: File "mypy/semanal_main.py", line 144, in semantic_analysis_for_targets + # types: File "mypy/semanal_main.py", line 291, in process_top_level_function + # types: File "mypy/semanal_main.py", line 349, in semantic_analyze_target + # types: File "mypy/semanal.py", line 603, in refresh_partial + # types: File "mypy/semanal.py", line 6453, in accept + # types: File "mypy/errors.py", line 1177, in report_internal_error + # types: File "mypy/semanal.py", line 6451, in accept + # types: File "mypy/nodes.py", line 786, in accept + # types: File "mypy/semanal.py", line 833, in visit_func_def + # types: File "mypy/semanal.py", line 865, in analyze_func_def + # types: File "mypy/typeanal.py", line 950, in visit_callable_type + # types: File "mypy/typeanal.py", line 1522, in anal_type + # types: File "mypy/types.py", line 2294, in accept + # types: File "mypy/typeanal.py", line 1051, in visit_tuple_type + # types: File "mypy/typeanal.py", line 1512, in anal_array + # types: File "mypy/typeanal.py", line 1522, in anal_type + # types: File "mypy/types.py", line 1917, in accept + # types: File "mypy/typeanal.py", line 931, in visit_callable_type + # types: File "mypy/typeanal.py", line 1469, in bind_function_type_variables + # types: AssertionError + # types: During handling of the above exception, another exception occurred: + # types: Traceback (most recent call last): + # types: File "mypy/dmypy_server.py", line 234, in serve + # types: File "mypy/dmypy_server.py", line 281, in run_command + # types: File "mypy/dmypy_server.py", line 931, in cmd_suggest + # types: File "mypy/suggestions.py", line 265, in suggest + # types: File "/usr/lib/python3.11/contextlib.py", line 155, in __exit__ + # types: self.gen.throw(typ, value, traceback) + # types: File "mypy/suggestions.py", line 298, in restore_after + # types: File "mypy/suggestions.py", line 687, in reload + # types: File "mypy/server/update.py", line 267, in update + # types: File "mypy/server/update.py", line 369, in update_one + # types: File "mypy/server/update.py", line 426, in update_module + # types: File "mypy/server/astdiff.py", line 223, in snapshot_symbol_table + # types: File "mypy/server/astdiff.py", line 236, in snapshot_definition + # types: File "mypy/server/astdiff.py", line 315, in snapshot_type + # types: File "mypy/types.py", line 1917, in accept + # types: File "mypy/server/astdiff.py", line 439, in visit_callable_type + # types: File "mypy/types.py", line 1900, in is_type_obj + # types: File "mypy/nodes.py", line 3170, in is_metaclass + # types: File "mypy/nodes.py", line 3180, in has_base + # types: AttributeError: attribute 'mro' of 'TypeInfo' undefined def __reduce__(self): return ( self.__new__, @@ -230,12 +342,13 @@ def __reduce__(self): {"collapse": self.collapse}, ) - def __str__(self): + def __str__(self) -> str: return ", ".join(repr(exc) for exc in self.exceptions) - def __repr__(self): + def __repr__(self) -> str: return f"" + # types: no-untyped-def error: Function is missing a type annotation def derive(self, __excs): # We use _collapse=False here to get ExceptionGroup semantics, since derive() # is part of the PEP 654 API @@ -244,7 +357,9 @@ def derive(self, __excs): return exc @classmethod - def filter(cls, handler, root_exc): + def filter( + cls, handler: Callable[[BaseException], BaseException], root_exc: BaseException + ) -> BaseException | None: """Apply the given ``handler`` to all the exceptions in ``root_exc``. Args: @@ -268,7 +383,9 @@ def filter(cls, handler, root_exc): return _filter_impl(handler, root_exc) @classmethod - def catch(cls, handler): + def catch( + cls, handler: Callable[[BaseException], BaseException] + ) -> MultiErrorCatcher: """Return a context manager that catches and re-throws exceptions after running :meth:`filter` on them. @@ -286,6 +403,7 @@ def catch(cls, handler): return MultiErrorCatcher(handler) +# types: type-arg error: Missing type parameters for generic type "ExceptionGroup" class NonBaseMultiError(MultiError, ExceptionGroup): pass @@ -314,6 +432,8 @@ class NonBaseMultiError(MultiError, ExceptionGroup): # https://github.com/pallets/jinja/blob/master/jinja2/debug.py try: + # types: import error: Cannot find implementation or library stub for module named "tputil" + # types: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports import tputil except ImportError: have_tproxy = False @@ -322,7 +442,8 @@ class NonBaseMultiError(MultiError, ExceptionGroup): if have_tproxy: # http://doc.pypy.org/en/latest/objspace-proxies.html - def copy_tb(base_tb, tb_next): + def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> tputil: + # types: no-untyped-def error: Function is missing a type annotation def controller(operation): # Rationale for pragma: I looked fairly carefully and tried a few # things, and AFAICT it's not actually possible to get any @@ -397,7 +518,9 @@ def copy_tb(base_tb, tb_next): del new_tb, old_tb_frame -def concat_tb(head, tail): +def concat_tb( + head: TracebackType | None, tail: TracebackType | None +) -> TracebackType | None: # We have to use an iterative algorithm here, because in the worst case # this might be a RecursionError stack that is by definition too deep to # process by recursion! @@ -417,7 +540,9 @@ def concat_tb(head, tail): if "IPython" in sys.modules: import IPython + # types: attr-defined error: Module "IPython" does not explicitly export attribute "get_ipython" ip = IPython.get_ipython() + # types: ^^^^^ if ip is not None: if ip.custom_exceptions != (): warnings.warn( @@ -428,8 +553,15 @@ def concat_tb(head, tail): category=RuntimeWarning, ) else: - - def trio_show_traceback(self, etype, value, tb, tb_offset=None): + # types: name-defined error: Name "Any" is not defined + # types: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Any") + def trio_show_traceback( + self: Any, + etype: Any, + value: BaseException, + tb: TracebackType, + tb_offset: Any | None = None, + ) -> None: # XX it would be better to integrate with IPython's fancy # exception formatting stuff (and not ignore tb_offset) print_exception(value) From a3c89639020935c534098e7f6cab8a4014e434fe Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 20:10:18 -0500 Subject: [PATCH 02/13] Finish work on multierror --- trio/_core/_multierror.py | 179 +++++++++++--------------------------- 1 file changed, 53 insertions(+), 126 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 1791883aee..c3be839020 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -3,7 +3,7 @@ import sys import warnings from collections.abc import Callable, Iterable -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING, Any import attr @@ -17,6 +17,7 @@ if TYPE_CHECKING: from types import TracebackType + from mypy_extensions import DefaultNamedArg from typing_extensions import Self ################################################################ # MultiError @@ -24,7 +25,7 @@ def _filter_impl( - handler: Callable[[BaseException], BaseException], root_exc: BaseException + handler: Callable[[BaseException], BaseException | None], root_exc: BaseException ) -> BaseException | None: # We have a tree of MultiError's, like: # @@ -139,7 +140,7 @@ def push_tb_down( # frame show up in the traceback; otherwise, we leave no trace.) @attr.s(frozen=True) class MultiErrorCatcher: - _handler: Callable[[BaseException], BaseException] = attr.ib() + handler: Callable[[BaseException], BaseException | None] = attr.ib() def __enter__(self) -> None: pass @@ -151,7 +152,7 @@ def __exit__( traceback: TracebackType | None, ) -> bool | None: if exc_value is not None: - filtered_exc = _filter_impl(self._handler, exc_value) + filtered_exc = _filter_impl(self.handler, exc_value) if filtered_exc is exc_value: # Let the interpreter re-raise it @@ -176,52 +177,13 @@ def __exit__( return False -# types: type-arg error: Missing type parameters for generic type "BaseExceptionGroup" -# types: note: Another file has errors: trio/_timeouts.py -# types: note: Another file has errors: trio/_core/_entry_queue.py -# types: note: Another file has errors: trio/_signals.py -# types: note: Another file has errors: trio/_util.py -# types: note: Another file has errors: trio/_subprocess_platform/waitid.py -# types: note: Another file has errors: trio/testing/__init__.py -# types: note: Another file has errors: trio/_core/_thread_cache.py -# types: note: Another file has errors: trio/_channel.py -# types: note: Another file has errors: trio/testing/_network.py -# types: note: Another file has errors: trio/_core/_wakeup_socketpair.py -# types: note: Another file has errors: trio/_subprocess.py -# types: note: Another file has errors: trio/_core/_unbounded_queue.py -# types: note: Another file has errors: trio/_path.py -# types: note: Another file has errors: trio/testing/_memory_streams.py -# types: note: Another file has errors: trio/_highlevel_open_unix_stream.py -# types: note: Another file has errors: trio/testing/_checkpoints.py -# types: note: Another file has errors: trio/_core/_traps.py -# types: note: Another file has errors: trio/_highlevel_serve_listeners.py -# types: note: Another file has errors: trio/_core/_generated_instrumentation.py -# types: note: Another file has errors: trio/_socket.py -# types: note: Another file has errors: trio/_core/_io_common.py -# types: note: Another file has errors: trio/lowlevel.py -# types: note: Another file has errors: trio/_core/_ki.py -# types: note: Another file has errors: trio/_core/_asyncgens.py -# types: note: Another file has errors: trio/_highlevel_ssl_helpers.py -# types: note: Another file has errors: trio/_ssl.py -# types: note: Another file has errors: trio/testing/_trio_test.py -# types: note: Another file has errors: trio/_core/_parking_lot.py -# types: note: Another file has errors: trio/_highlevel_open_tcp_listeners.py -# types: note: Another file has errors: trio/testing/_check_streams.py -# types: note: Another file has errors: trio/_highlevel_open_tcp_stream.py -# types: note: Another file has errors: trio/_core/_generated_io_epoll.py -# types: note: Another file has errors: trio/_core/_mock_clock.py -# types: note: Another file has errors: trio/_sync.py -# types: note: Another file has errors: trio/_threads.py -# types: note: Another file has errors: trio/_dtls.py -# types: note: Another file has errors: trio/_core/_run.py -# types: note: Another file has errors: trio/__init__.py -# types: note: Another file has errors: trio/_core/_generated_run.py -# types: note: Another file has errors: trio/_highlevel_socket.py -# types: note: Another file has errors: trio/_unix_pipes.py -# types: note: Another file has errors: trio/_core/_io_epoll.py -# types: note: Another file has errors: trio/testing/_sequencer.py -# types: type-arg error: Missing type parameters for generic type "BaseExceptionGroup" -class MultiError(BaseExceptionGroup): +if TYPE_CHECKING: + _BaseExceptionGroup = BaseExceptionGroup[BaseException] +else: + _BaseExceptionGroup = BaseExceptionGroup + + +class MultiError(_BaseExceptionGroup): """An exception that contains other exceptions; also known as an "inception". @@ -257,9 +219,9 @@ def __init__( super().__init__("multiple tasks failed", exceptions) - def __new__( + def __new__( # type: ignore[misc] # mypy says __new__ must return a class instance cls, exceptions: Iterable[BaseException], *, _collapse: bool = True - ) -> MultiError | Self: + ) -> NonBaseMultiError | MultiError | Self: exceptions = list(exceptions) for exc in exceptions: if not isinstance(exc, BaseException): @@ -278,64 +240,29 @@ def __new__( # In an earlier version of the code, we didn't define __init__ and # simply set the `exceptions` attribute directly on the new object. # However, linters expect attributes to be initialized in __init__. + from_class: type[Self] | type[NonBaseMultiError] = cls if all(isinstance(exc, Exception) for exc in exceptions): - cls = NonBaseMultiError - - return super().__new__(cls, "multiple tasks failed", exceptions) - - # types: Error running mypy: Daemon crashed! - # types: Traceback (most recent call last): - # types: File "mypy/suggestions.py", line 296, in restore_after - # types: File "mypy/suggestions.py", line 267, in suggest - # types: File "mypy/suggestions.py", line 502, in get_suggestion - # types: File "mypy/suggestions.py", line 444, in find_best - # types: File "mypy/suggestions.py", line 675, in try_type - # types: File "mypy/server/update.py", line 314, in trigger - # types: File "mypy/server/update.py", line 881, in propagate_changes_using_dependencies - # types: File "mypy/server/update.py", line 1010, in reprocess_nodes - # types: File "mypy/semanal_main.py", line 144, in semantic_analysis_for_targets - # types: File "mypy/semanal_main.py", line 291, in process_top_level_function - # types: File "mypy/semanal_main.py", line 349, in semantic_analyze_target - # types: File "mypy/semanal.py", line 603, in refresh_partial - # types: File "mypy/semanal.py", line 6453, in accept - # types: File "mypy/errors.py", line 1177, in report_internal_error - # types: File "mypy/semanal.py", line 6451, in accept - # types: File "mypy/nodes.py", line 786, in accept - # types: File "mypy/semanal.py", line 833, in visit_func_def - # types: File "mypy/semanal.py", line 865, in analyze_func_def - # types: File "mypy/typeanal.py", line 950, in visit_callable_type - # types: File "mypy/typeanal.py", line 1522, in anal_type - # types: File "mypy/types.py", line 2294, in accept - # types: File "mypy/typeanal.py", line 1051, in visit_tuple_type - # types: File "mypy/typeanal.py", line 1512, in anal_array - # types: File "mypy/typeanal.py", line 1522, in anal_type - # types: File "mypy/types.py", line 1917, in accept - # types: File "mypy/typeanal.py", line 931, in visit_callable_type - # types: File "mypy/typeanal.py", line 1469, in bind_function_type_variables - # types: AssertionError - # types: During handling of the above exception, another exception occurred: - # types: Traceback (most recent call last): - # types: File "mypy/dmypy_server.py", line 234, in serve - # types: File "mypy/dmypy_server.py", line 281, in run_command - # types: File "mypy/dmypy_server.py", line 931, in cmd_suggest - # types: File "mypy/suggestions.py", line 265, in suggest - # types: File "/usr/lib/python3.11/contextlib.py", line 155, in __exit__ - # types: self.gen.throw(typ, value, traceback) - # types: File "mypy/suggestions.py", line 298, in restore_after - # types: File "mypy/suggestions.py", line 687, in reload - # types: File "mypy/server/update.py", line 267, in update - # types: File "mypy/server/update.py", line 369, in update_one - # types: File "mypy/server/update.py", line 426, in update_module - # types: File "mypy/server/astdiff.py", line 223, in snapshot_symbol_table - # types: File "mypy/server/astdiff.py", line 236, in snapshot_definition - # types: File "mypy/server/astdiff.py", line 315, in snapshot_type - # types: File "mypy/types.py", line 1917, in accept - # types: File "mypy/server/astdiff.py", line 439, in visit_callable_type - # types: File "mypy/types.py", line 1900, in is_type_obj - # types: File "mypy/nodes.py", line 3170, in is_metaclass - # types: File "mypy/nodes.py", line 3180, in has_base - # types: AttributeError: attribute 'mro' of 'TypeInfo' undefined - def __reduce__(self): + from_class = NonBaseMultiError + + # Mypy is really mad about the following line: + # Ignoring arg-type: 'Argument 3 to "__new__" of "BaseExceptionGroup" has incompatible type "list[BaseException]"; expected "Sequence[_BaseExceptionT_co]"' + # We have checked that exceptions is indeed a list of BaseException objects, this is fine. + # Ignoring type-var: 'Value of type variable "Self" of "__new__" of "BaseExceptionGroup" cannot be "object"' + # Not sure how mypy is getting 'object', this is also fine. + new_obj = super().__new__(from_class, "multiple tasks failed", exceptions) # type: ignore[arg-type,type-var] + assert isinstance(new_obj, (cls, NonBaseMultiError)) + return new_obj + + def __reduce__( + self, + ) -> tuple[ + Callable[ + [type[Self], Iterable[BaseException], DefaultNamedArg(bool, "_collapse")], + NonBaseMultiError | MultiError | Self, + ], + tuple[type[Self], list[BaseException]], + dict[str, bool], + ]: return ( self.__new__, (self.__class__, list(self.exceptions)), @@ -348,8 +275,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"" - # types: no-untyped-def error: Function is missing a type annotation - def derive(self, __excs): + def derive(self, __excs: list[BaseException]) -> MultiError: # type: ignore[override] # We use _collapse=False here to get ExceptionGroup semantics, since derive() # is part of the PEP 654 API exc = MultiError(__excs, _collapse=False) @@ -358,7 +284,9 @@ def derive(self, __excs): @classmethod def filter( - cls, handler: Callable[[BaseException], BaseException], root_exc: BaseException + cls, + handler: Callable[[BaseException], BaseException | None], + root_exc: BaseException, ) -> BaseException | None: """Apply the given ``handler`` to all the exceptions in ``root_exc``. @@ -384,7 +312,7 @@ def filter( @classmethod def catch( - cls, handler: Callable[[BaseException], BaseException] + cls, handler: Callable[[BaseException], BaseException | None] ) -> MultiErrorCatcher: """Return a context manager that catches and re-throws exceptions after running :meth:`filter` on them. @@ -403,9 +331,14 @@ def catch( return MultiErrorCatcher(handler) -# types: type-arg error: Missing type parameters for generic type "ExceptionGroup" -class NonBaseMultiError(MultiError, ExceptionGroup): - pass +if TYPE_CHECKING: + _ExceptionGroup = ExceptionGroup[Exception] +else: + _ExceptionGroup = ExceptionGroup + + +class NonBaseMultiError(MultiError, _ExceptionGroup): + __slots__ = () # Clean up exception printing: @@ -432,8 +365,6 @@ class NonBaseMultiError(MultiError, ExceptionGroup): # https://github.com/pallets/jinja/blob/master/jinja2/debug.py try: - # types: import error: Cannot find implementation or library stub for module named "tputil" - # types: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports import tputil except ImportError: have_tproxy = False @@ -443,8 +374,7 @@ class NonBaseMultiError(MultiError, ExceptionGroup): if have_tproxy: # http://doc.pypy.org/en/latest/objspace-proxies.html def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> tputil: - # types: no-untyped-def error: Function is missing a type annotation - def controller(operation): + def controller(operation: tputil.ProxyOperation) -> TracebackType | None | Any: # Rationale for pragma: I looked fairly carefully and tried a few # things, and AFAICT it's not actually possible to get any # 'opname' that isn't __getattr__ or __getattribute__. So there's @@ -480,7 +410,7 @@ class CTraceback(ctypes.Structure): ("tb_lineno", ctypes.c_int), ] - def copy_tb(base_tb, tb_next): + def copy_tb(base_tb: TracebackType, tb_next: TracebackType) -> TracebackType: # TracebackType has no public constructor, so allocate one the hard way try: raise ValueError @@ -540,9 +470,7 @@ def concat_tb( if "IPython" in sys.modules: import IPython - # types: attr-defined error: Module "IPython" does not explicitly export attribute "get_ipython" - ip = IPython.get_ipython() - # types: ^^^^^ + ip = IPython.get_ipython() # type: ignore[attr-defined] # not explicitly exported if ip is not None: if ip.custom_exceptions != (): warnings.warn( @@ -553,8 +481,7 @@ def concat_tb( category=RuntimeWarning, ) else: - # types: name-defined error: Name "Any" is not defined - # types: note: Did you forget to import it from "typing"? (Suggestion: "from typing import Any") + def trio_show_traceback( self: Any, etype: Any, From b21b2936e03320cffe5454697a67e7ab6e00f799 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 20:28:34 -0500 Subject: [PATCH 03/13] Few fixes --- pyproject.toml | 3 ++- trio/_core/_multierror.py | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a23f7f5db9..9af051c5b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,8 +52,9 @@ module = [ "trio._abc", "trio._core._entry_queue", "trio._core._local", - "trio._core._unbounded_queue", + "trio._core._multierror", "trio._core._thread_cache", + "trio._core._unbounded_queue", "trio._deprecate", "trio._dtls", "trio._file_io", diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index c3be839020..cd6f27eece 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -2,8 +2,8 @@ import sys import warnings -from collections.abc import Callable, Iterable -from typing import TYPE_CHECKING, Any +from collections.abc import Callable, Iterable, Sequence +from typing import TYPE_CHECKING, Any, overload import attr @@ -18,7 +18,7 @@ from types import TracebackType from mypy_extensions import DefaultNamedArg - from typing_extensions import Self + from typing_extensions import Literal, Self ################################################################ # MultiError ################################################################ @@ -221,7 +221,7 @@ def __init__( def __new__( # type: ignore[misc] # mypy says __new__ must return a class instance cls, exceptions: Iterable[BaseException], *, _collapse: bool = True - ) -> NonBaseMultiError | MultiError | Self: + ) -> NonBaseMultiError | Self: exceptions = list(exceptions) for exc in exceptions: if not isinstance(exc, BaseException): @@ -231,7 +231,7 @@ def __new__( # type: ignore[misc] # mypy says __new__ must return a class inst # Python will implicitly call our __init__ on it again. See # special handling in __init__. single = exceptions[0] - assert isinstance(single, MultiError) + assert isinstance(single, cls) return single else: # The base class __new__() implicitly invokes our __init__, which @@ -275,10 +275,18 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"" - def derive(self, __excs: list[BaseException]) -> MultiError: # type: ignore[override] + @overload # type: ignore[override] # Basically mypy is mad return type is not exactly the same as superclass + def derive(self, __excs: Sequence[Exception]) -> NonBaseMultiError: + ... + + @overload + def derive(self, __excs: Sequence[BaseException]) -> MultiError: + ... + + def derive(self, __excs: Sequence[Exception | BaseException]) -> MultiError: # We use _collapse=False here to get ExceptionGroup semantics, since derive() # is part of the PEP 654 API - exc = MultiError(__excs, _collapse=False) + exc = MultiError(list(__excs), _collapse=False) exc.collapse = self.collapse return exc From e0f7a9cac32647c303af28f101600ade0331a45e Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:30:55 -0500 Subject: [PATCH 04/13] Fix incorrect assumptions --- trio/_core/_multierror.py | 45 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index cd6f27eece..3a621b8bae 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -3,7 +3,7 @@ import sys import warnings from collections.abc import Callable, Iterable, Sequence -from typing import TYPE_CHECKING, Any, overload +from typing import TYPE_CHECKING, Any, cast, overload import attr @@ -18,7 +18,7 @@ from types import TracebackType from mypy_extensions import DefaultNamedArg - from typing_extensions import Literal, Self + from typing_extensions import Self ################################################################ # MultiError ################################################################ @@ -140,7 +140,7 @@ def push_tb_down( # frame show up in the traceback; otherwise, we leave no trace.) @attr.s(frozen=True) class MultiErrorCatcher: - handler: Callable[[BaseException], BaseException | None] = attr.ib() + _handler: Callable[[BaseException], BaseException | None] = attr.ib() def __enter__(self) -> None: pass @@ -152,7 +152,7 @@ def __exit__( traceback: TracebackType | None, ) -> bool | None: if exc_value is not None: - filtered_exc = _filter_impl(self.handler, exc_value) + filtered_exc = _filter_impl(self._handler, exc_value) if filtered_exc is exc_value: # Let the interpreter re-raise it @@ -221,7 +221,7 @@ def __init__( def __new__( # type: ignore[misc] # mypy says __new__ must return a class instance cls, exceptions: Iterable[BaseException], *, _collapse: bool = True - ) -> NonBaseMultiError | Self: + ) -> NonBaseMultiError | Self | BaseException: exceptions = list(exceptions) for exc in exceptions: if not isinstance(exc, BaseException): @@ -230,9 +230,7 @@ def __new__( # type: ignore[misc] # mypy says __new__ must return a class inst # If this lone object happens to itself be a MultiError, then # Python will implicitly call our __init__ on it again. See # special handling in __init__. - single = exceptions[0] - assert isinstance(single, cls) - return single + return exceptions[0] else: # The base class __new__() implicitly invokes our __init__, which # is what we want. @@ -255,14 +253,7 @@ def __new__( # type: ignore[misc] # mypy says __new__ must return a class inst def __reduce__( self, - ) -> tuple[ - Callable[ - [type[Self], Iterable[BaseException], DefaultNamedArg(bool, "_collapse")], - NonBaseMultiError | MultiError | Self, - ], - tuple[type[Self], list[BaseException]], - dict[str, bool], - ]: + ) -> tuple[object, tuple[type[Self], list[BaseException]], dict[str, bool],]: return ( self.__new__, (self.__class__, list(self.exceptions)), @@ -283,7 +274,9 @@ def derive(self, __excs: Sequence[Exception]) -> NonBaseMultiError: def derive(self, __excs: Sequence[BaseException]) -> MultiError: ... - def derive(self, __excs: Sequence[Exception | BaseException]) -> MultiError: + def derive( + self, __excs: Sequence[Exception | BaseException] + ) -> NonBaseMultiError | MultiError: # We use _collapse=False here to get ExceptionGroup semantics, since derive() # is part of the PEP 654 API exc = MultiError(list(__excs), _collapse=False) @@ -381,8 +374,8 @@ class NonBaseMultiError(MultiError, _ExceptionGroup): if have_tproxy: # http://doc.pypy.org/en/latest/objspace-proxies.html - def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> tputil: - def controller(operation: tputil.ProxyOperation) -> TracebackType | None | Any: + def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: + def controller(operation: tputil.ProxyOperation) -> Any | None: # Rationale for pragma: I looked fairly carefully and tried a few # things, and AFAICT it's not actually possible to get any # 'opname' that isn't __getattr__ or __getattribute__. So there's @@ -394,9 +387,11 @@ def controller(operation: tputil.ProxyOperation) -> TracebackType | None | Any: ]: # pragma: no cover if operation.args[0] == "tb_next": return tb_next - return operation.delegate() + return operation.delegate() # Deligate is reverting to original behaviour - return tputil.make_proxy(controller, type(base_tb), base_tb) + return cast( + TracebackType, tputil.make_proxy(controller, type(base_tb), base_tb) + ) # Returns proxy to traceback else: # ctypes it is @@ -418,7 +413,7 @@ class CTraceback(ctypes.Structure): ("tb_lineno", ctypes.c_int), ] - def copy_tb(base_tb: TracebackType, tb_next: TracebackType) -> TracebackType: + def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: # TracebackType has no public constructor, so allocate one the hard way try: raise ValueError @@ -491,11 +486,11 @@ def concat_tb( else: def trio_show_traceback( - self: Any, - etype: Any, + self: IPython.core.interactiveshell.InteractiveShell, + etype: type[BaseException], value: BaseException, tb: TracebackType, - tb_offset: Any | None = None, + tb_offset: int | None = None, ) -> None: # XX it would be better to integrate with IPython's fancy # exception formatting stuff (and not ignore tb_offset) From b52d133cf4b4cf25458cb2cbb4da89ac467f62eb Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:32:50 -0500 Subject: [PATCH 05/13] Remove unused import --- trio/_core/_multierror.py | 1 - 1 file changed, 1 deletion(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 3a621b8bae..6194127e07 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -17,7 +17,6 @@ if TYPE_CHECKING: from types import TracebackType - from mypy_extensions import DefaultNamedArg from typing_extensions import Self ################################################################ # MultiError From d49b65e2708bc686a1a1ae9be9e839aa4b7d0ea3 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:48:02 -0500 Subject: [PATCH 06/13] Fix issues from CI --- trio/_core/_multierror.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 6194127e07..ca671b175b 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -3,6 +3,7 @@ import sys import warnings from collections.abc import Callable, Iterable, Sequence +from types import TracebackType from typing import TYPE_CHECKING, Any, cast, overload import attr @@ -15,8 +16,6 @@ from traceback import print_exception if TYPE_CHECKING: - from types import TracebackType - from typing_extensions import Self ################################################################ # MultiError @@ -241,12 +240,9 @@ def __new__( # type: ignore[misc] # mypy says __new__ must return a class inst if all(isinstance(exc, Exception) for exc in exceptions): from_class = NonBaseMultiError - # Mypy is really mad about the following line: # Ignoring arg-type: 'Argument 3 to "__new__" of "BaseExceptionGroup" has incompatible type "list[BaseException]"; expected "Sequence[_BaseExceptionT_co]"' # We have checked that exceptions is indeed a list of BaseException objects, this is fine. - # Ignoring type-var: 'Value of type variable "Self" of "__new__" of "BaseExceptionGroup" cannot be "object"' - # Not sure how mypy is getting 'object', this is also fine. - new_obj = super().__new__(from_class, "multiple tasks failed", exceptions) # type: ignore[arg-type,type-var] + new_obj = super().__new__(from_class, "multiple tasks failed", exceptions) # type: ignore[arg-type] assert isinstance(new_obj, (cls, NonBaseMultiError)) return new_obj @@ -372,6 +368,9 @@ class NonBaseMultiError(MultiError, _ExceptionGroup): have_tproxy = True if have_tproxy: + if TYPE_CHECKING: + import tputil + # http://doc.pypy.org/en/latest/objspace-proxies.html def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: def controller(operation: tputil.ProxyOperation) -> Any | None: @@ -430,14 +429,14 @@ def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackT # which it already is, so we're done. Otherwise, we have to actually # do some work: if tb_next is not None: - _ctypes.Py_INCREF(tb_next) + _ctypes.Py_INCREF(tb_next) # type: ignore[attr-defined] c_new_tb.tb_next = id(tb_next) assert c_new_tb.tb_frame is not None - _ctypes.Py_INCREF(base_tb.tb_frame) + _ctypes.Py_INCREF(base_tb.tb_frame) # type: ignore[attr-defined] old_tb_frame = new_tb.tb_frame c_new_tb.tb_frame = id(base_tb.tb_frame) - _ctypes.Py_DECREF(old_tb_frame) + _ctypes.Py_DECREF(old_tb_frame) # type: ignore[attr-defined] c_new_tb.tb_lasti = base_tb.tb_lasti c_new_tb.tb_lineno = base_tb.tb_lineno @@ -472,7 +471,7 @@ def concat_tb( if "IPython" in sys.modules: import IPython - ip = IPython.get_ipython() # type: ignore[attr-defined] # not explicitly exported + ip = IPython.get_ipython() if ip is not None: if ip.custom_exceptions != (): warnings.warn( @@ -521,7 +520,9 @@ def trio_show_traceback( assert sys.excepthook is apport_python_hook.apport_excepthook - def replacement_excepthook(etype, value, tb): + def replacement_excepthook( + etype: type[BaseException], value: BaseException, tb: TracebackType + ) -> None: sys.stderr.write("".join(format_exception(etype, value, tb))) fake_sys = ModuleType("trio_fake_sys") From 15a629ad7566599fcdc5fbaf70041a3254166de6 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:10:47 -0500 Subject: [PATCH 07/13] More fixes from CI --- trio/_core/_multierror.py | 57 +++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index ca671b175b..bb9e5d09ee 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -363,35 +363,6 @@ class NonBaseMultiError(MultiError, _ExceptionGroup): try: import tputil except ImportError: - have_tproxy = False -else: - have_tproxy = True - -if have_tproxy: - if TYPE_CHECKING: - import tputil - - # http://doc.pypy.org/en/latest/objspace-proxies.html - def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: - def controller(operation: tputil.ProxyOperation) -> Any | None: - # Rationale for pragma: I looked fairly carefully and tried a few - # things, and AFAICT it's not actually possible to get any - # 'opname' that isn't __getattr__ or __getattribute__. So there's - # no missing test we could add, and no value in coverage nagging - # us about adding one. - if operation.opname in [ - "__getattribute__", - "__getattr__", - ]: # pragma: no cover - if operation.args[0] == "tb_next": - return tb_next - return operation.delegate() # Deligate is reverting to original behaviour - - return cast( - TracebackType, tputil.make_proxy(controller, type(base_tb), base_tb) - ) # Returns proxy to traceback - -else: # ctypes it is import ctypes @@ -417,6 +388,7 @@ def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackT raise ValueError except ValueError as exc: new_tb = exc.__traceback__ + assert new_tb is not None c_new_tb = CTraceback.from_address(id(new_tb)) # At the C level, tb_next either pointer to the next traceback or is @@ -442,12 +414,33 @@ def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackT c_new_tb.tb_lineno = base_tb.tb_lineno try: - return new_tb + return cast(TracebackType, new_tb) finally: # delete references from locals to avoid creating cycles # see test_MultiError_catch_doesnt_create_cyclic_garbage del new_tb, old_tb_frame +else: + # http://doc.pypy.org/en/latest/objspace-proxies.html + def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: + def controller(operation: tputil.ProxyOperation) -> Any | None: + # Rationale for pragma: I looked fairly carefully and tried a few + # things, and AFAICT it's not actually possible to get any + # 'opname' that isn't __getattr__ or __getattribute__. So there's + # no missing test we could add, and no value in coverage nagging + # us about adding one. + if operation.opname in [ + "__getattribute__", + "__getattr__", + ]: # pragma: no cover + if operation.args[0] == "tb_next": + return tb_next + return operation.delegate() # Deligate is reverting to original behaviour + + return cast( + TracebackType, tputil.make_proxy(controller, type(base_tb), base_tb) + ) # Returns proxy to traceback + def concat_tb( head: TracebackType | None, tail: TracebackType | None @@ -521,11 +514,11 @@ def trio_show_traceback( assert sys.excepthook is apport_python_hook.apport_excepthook def replacement_excepthook( - etype: type[BaseException], value: BaseException, tb: TracebackType + etype: type[BaseException], value: BaseException, tb: TracebackType | None ) -> None: sys.stderr.write("".join(format_exception(etype, value, tb))) fake_sys = ModuleType("trio_fake_sys") fake_sys.__dict__.update(sys.__dict__) - fake_sys.__excepthook__ = replacement_excepthook # type: ignore + fake_sys.__excepthook__ = replacement_excepthook apport_python_hook.sys = fake_sys From 77cf68304151565436fee8b2322a4ffe51b37626 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:18:48 -0500 Subject: [PATCH 08/13] Fixes from CI no. 3 --- trio/_core/_multierror.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index bb9e5d09ee..8cbf411fc6 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -261,7 +261,7 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"" - @overload # type: ignore[override] # Basically mypy is mad return type is not exactly the same as superclass + @overload def derive(self, __excs: Sequence[Exception]) -> NonBaseMultiError: ... @@ -414,16 +414,19 @@ def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackT c_new_tb.tb_lineno = base_tb.tb_lineno try: - return cast(TracebackType, new_tb) + return new_tb finally: # delete references from locals to avoid creating cycles # see test_MultiError_catch_doesnt_create_cyclic_garbage del new_tb, old_tb_frame else: + if TYPE_CHECKING: + from tputil import ProxyOperation + # http://doc.pypy.org/en/latest/objspace-proxies.html def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: - def controller(operation: tputil.ProxyOperation) -> Any | None: + def controller(operation: ProxyOperation) -> Any | None: # Rationale for pragma: I looked fairly carefully and tried a few # things, and AFAICT it's not actually possible to get any # 'opname' that isn't __getattr__ or __getattribute__. So there's @@ -516,9 +519,11 @@ def trio_show_traceback( def replacement_excepthook( etype: type[BaseException], value: BaseException, tb: TracebackType | None ) -> None: - sys.stderr.write("".join(format_exception(etype, value, tb))) + # This does work, it's an overloaded function + sys.stderr.write("".join(format_exception(etype, value, tb))) # type: ignore[arg-type] fake_sys = ModuleType("trio_fake_sys") fake_sys.__dict__.update(sys.__dict__) - fake_sys.__excepthook__ = replacement_excepthook + # Fake does not have __excepthook__ attribute, but we are about to replace real sys + fake_sys.__excepthook__ = replacement_excepthook # type: ignore[attr-defined] apport_python_hook.sys = fake_sys From 9b2c32d5b26163dbdd021d0bddc0f2648823d74a Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:29:08 -0500 Subject: [PATCH 09/13] Ignore `tputil.ProxyOperation` not being able to import error (lies) --- trio/_core/_multierror.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 8cbf411fc6..a1e6545a86 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -421,12 +421,10 @@ def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackT del new_tb, old_tb_frame else: - if TYPE_CHECKING: - from tputil import ProxyOperation - # http://doc.pypy.org/en/latest/objspace-proxies.html def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: - def controller(operation: ProxyOperation) -> Any | None: + # Mypy refuses to believe that ProxyOperation can be imported properly + def controller(operation: tputil.ProxyOperation) -> Any | None: # type: ignore[no-any-unimported] # Rationale for pragma: I looked fairly carefully and tried a few # things, and AFAICT it's not actually possible to get any # 'opname' that isn't __getattr__ or __getattribute__. So there's From 5ad1cab84968e3ce6384e95abb433802e0448c43 Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:58:33 -0500 Subject: [PATCH 10/13] Fixes suggested by @A5rocks --- trio/_core/_multierror.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index a1e6545a86..c09e5b2d23 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -204,8 +204,10 @@ class MultiError(_BaseExceptionGroup): """ + __slots__ = ("collapse",) + def __init__( - self, exceptions: list[BaseException], *, _collapse: bool = True + self, exceptions: Sequence[BaseException], *, _collapse: bool = True ) -> None: self.collapse = _collapse @@ -218,7 +220,7 @@ def __init__( super().__init__("multiple tasks failed", exceptions) def __new__( # type: ignore[misc] # mypy says __new__ must return a class instance - cls, exceptions: Iterable[BaseException], *, _collapse: bool = True + cls, exceptions: Sequence[BaseException], *, _collapse: bool = True ) -> NonBaseMultiError | Self | BaseException: exceptions = list(exceptions) for exc in exceptions: @@ -248,7 +250,7 @@ def __new__( # type: ignore[misc] # mypy says __new__ must return a class inst def __reduce__( self, - ) -> tuple[object, tuple[type[Self], list[BaseException]], dict[str, bool],]: + ) -> tuple[object, tuple[type[Self], list[BaseException]], dict[str, bool]]: return ( self.__new__, (self.__class__, list(self.exceptions)), @@ -262,19 +264,19 @@ def __repr__(self) -> str: return f"" @overload - def derive(self, __excs: Sequence[Exception]) -> NonBaseMultiError: + def derive(self, excs: Sequence[Exception], /) -> NonBaseMultiError: ... @overload - def derive(self, __excs: Sequence[BaseException]) -> MultiError: + def derive(self, excs: Sequence[BaseException], /) -> MultiError: ... def derive( - self, __excs: Sequence[Exception | BaseException] + self, excs: Sequence[Exception | BaseException], / ) -> NonBaseMultiError | MultiError: # We use _collapse=False here to get ExceptionGroup semantics, since derive() # is part of the PEP 654 API - exc = MultiError(list(__excs), _collapse=False) + exc = MultiError(excs, _collapse=False) exc.collapse = self.collapse return exc @@ -522,6 +524,6 @@ def replacement_excepthook( fake_sys = ModuleType("trio_fake_sys") fake_sys.__dict__.update(sys.__dict__) - # Fake does not have __excepthook__ attribute, but we are about to replace real sys + # Fake does have __excepthook__ after __dict__ update, but type checkers don't recognize this fake_sys.__excepthook__ = replacement_excepthook # type: ignore[attr-defined] apport_python_hook.sys = fake_sys From 073ecb7278a200293e92a9bf931838200261985a Mon Sep 17 00:00:00 2001 From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com> Date: Wed, 9 Aug 2023 09:02:41 -0500 Subject: [PATCH 11/13] Remove unused import --- trio/_core/_multierror.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index c09e5b2d23..e1854184cf 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -2,7 +2,7 @@ import sys import warnings -from collections.abc import Callable, Iterable, Sequence +from collections.abc import Callable, Sequence from types import TracebackType from typing import TYPE_CHECKING, Any, cast, overload From dd2825a50773aca6317b9d59a0632f6683ed5b9c Mon Sep 17 00:00:00 2001 From: CoolCat467 Date: Wed, 9 Aug 2023 10:46:56 -0500 Subject: [PATCH 12/13] Remove slots, causes test failures --- trio/_core/_multierror.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index e1854184cf..1dd675f474 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -204,8 +204,6 @@ class MultiError(_BaseExceptionGroup): """ - __slots__ = ("collapse",) - def __init__( self, exceptions: Sequence[BaseException], *, _collapse: bool = True ) -> None: From 7a2ec85d9c7e078304ac0632af7aefef8485338b Mon Sep 17 00:00:00 2001 From: jakkdl Date: Mon, 14 Aug 2023 14:06:05 +0200 Subject: [PATCH 13/13] remove type: ignore no-any-unimported for now --- trio/_core/_multierror.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/trio/_core/_multierror.py b/trio/_core/_multierror.py index 1dd675f474..6e4cb8b923 100644 --- a/trio/_core/_multierror.py +++ b/trio/_core/_multierror.py @@ -424,7 +424,8 @@ def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackT # http://doc.pypy.org/en/latest/objspace-proxies.html def copy_tb(base_tb: TracebackType, tb_next: TracebackType | None) -> TracebackType: # Mypy refuses to believe that ProxyOperation can be imported properly - def controller(operation: tputil.ProxyOperation) -> Any | None: # type: ignore[no-any-unimported] + # TODO: will need no-any-unimported if/when that's toggled on + def controller(operation: tputil.ProxyOperation) -> Any | None: # Rationale for pragma: I looked fairly carefully and tried a few # things, and AFAICT it's not actually possible to get any # 'opname' that isn't __getattr__ or __getattribute__. So there's