Prioritize WindowsNamedPipesNotAvailableError over other errors
Marco Ricci

Marco Ricci commited on 2026-01-18 15:42:53
Zeige 1 geänderte Dateien mit 26 Einfügungen und 9 Löschungen.


The SSH agent socket provider, by contract, must raise
`NotImplementedError` (or a subclass) if and only if the socket can
never be successfully constructed, on principle.  Conversely, if the
socket provider raises any other kind of error, then the socket *could*
be constructed on this system, principally.

In the specific case of Windows named pipes whose address is named by
the `SSH_AUTH_SOCK` environment variable, it is a programming error to
first check the environment variable value, allowing `KeyError` or
`ValueError` to bubble through to the caller, and only afterwards during
construction of the named pipe realize that there is no support.
... ...
@@ -409,7 +409,7 @@ class WindowsNamedPipeHandle:
409 409
 
410 410
         """
411 411
         if not name.replace("/", "\\").startswith(PIPE_PREFIX):
412
-            msg = f"Invalid named pipe address: {name!r}"
412
+            msg = f"Invalid named pipe name: {name!r}"
413 413
             raise ValueError(msg)
414 414
         self.handle: HANDLE | None = None
415 415
         while self.handle is None:
... ...
@@ -652,29 +652,50 @@ class SocketProvider:
652 652
 
653 653
     @staticmethod
654 654
     def _windows_named_pipe(
655
-        pipe_name: _WindowsNamedPipeSocketAddress | str,
655
+        pipe_name: _WindowsNamedPipeSocketAddress | str | None,
656 656
     ) -> _types.SSHAgentSocket:
657
-        """Return a socket wrapper around a Windows named pipe.
657
+        r"""Return a socket wrapper around a Windows named pipe.
658
+
659
+        Args:
660
+            pipe_name:
661
+                The named pipe's name, or a well-known named pipe socket
662
+                address, or `None` to look up the true name in
663
+                `SSH_AUTH_SOCK`.  In the latter case, the `SSH_AUTH_SOCK`
664
+                environment variable is assumed to contain a valid named
665
+                pipe name, i.e., a path starting with `\\.\pipe\` or
666
+                `//./pipe/`.
658 667
 
659 668
         Raises:
669
+            KeyError:
670
+                The `SSH_AUTH_SOCK` environment variable was not found.
660 671
             OSError:
661 672
                 There was an error setting up a connection to the agent.
673
+            ValueError:
674
+                The path clearly does not name a valid named pipe name.
662 675
             WindowsNamedPipesNotAvailableError:
663 676
                 This Python version does not support Windows named
664 677
                 pipes.
665 678
 
666 679
         """
680
+        # We must raise WindowsNamedPipesNotAvailableError in preference
681
+        # to other errors, so we must duplicate the system support check
682
+        # here (even though WindowsNamedPipeHandle would raise as well).
667 683
         if not hasattr(ctypes, "WinDLL"):
668 684
             msg = "This Python version does not support Windows named pipes."
669 685
             raise WindowsNamedPipesNotAvailableError(msg)
670 686
         else:  # noqa: RET506  # pragma: unless the-annoying-os no cover
671 687
             # TODO(the-13th-letter): Rewrite using structural pattern
672
-            # matching.
688
+            # matching (3 cases).
673 689
             # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9
674 690
             if pipe_name == _WindowsNamedPipeSocketAddress.PAGEANT:
675 691
                 return WindowsNamedPipeHandle.for_pageant()
676 692
             if pipe_name == _WindowsNamedPipeSocketAddress.OPENSSH:
677 693
                 return WindowsNamedPipeHandle.for_openssh()
694
+            if pipe_name is None:  # pragma: no branch
695
+                pipe_name = os.environ["SSH_AUTH_SOCK"]
696
+            if not pipe_name.replace("/", "\\").startswith(PIPE_PREFIX):
697
+                msg = f"Invalid named pipe name: {pipe_name!r}"
698
+                raise ValueError(msg)
678 699
             return WindowsNamedPipeHandle(pipe_name)
679 700
 
680 701
     @classmethod
... ...
@@ -728,11 +749,7 @@ class SocketProvider:
728 749
                 pipes.
729 750
 
730 751
         """
731
-        address = os.environ["SSH_AUTH_SOCK"]
732
-        if not address.replace("/", "\\").startswith(PIPE_PREFIX):
733
-            msg = f"Invalid named pipe name: {address!r}"
734
-            raise ValueError(msg)
735
-        return cls._windows_named_pipe(address)
752
+        return cls._windows_named_pipe(None)
736 753
 
737 754
     registry: ClassVar[
738 755
         dict[str, _types.SSHAgentSocketProvider | str | None]
739 756