22import re
33from collections .abc import Callable
44from dataclasses import dataclass
5+ from enum import Enum
56from typing import TypeVar
67
78from grpc import (
4344]
4445
4546
47+ class ChannelProtocol (Enum ):
48+ HTTPS = 1
49+ HTTP = 2
50+ UNIX = 3
51+
52+
4653@dataclass
4754class ChannelInfo :
48- url_without_protocol : str
49- use_ssl : bool
55+ address : str
56+ """GRPC target address. For http(s) connections this is the host[:port] format without a scheme. For unix sockets
57+ this is the unix socket path including the `unix://` prefix for absolute paths or `unix:` for relative paths."""
58+ port : int
59+ """Port number for http(s) connections. For unix sockets this is always 0."""
60+ protocol : ChannelProtocol
61+ """The protocol to use for the channel."""
5062
5163
5264def open_channel (url : str , auth_token : str | None = None ) -> Channel :
@@ -69,14 +81,22 @@ def open_channel(url: str, auth_token: str | None = None) -> Channel:
6981
7082
7183def _open_channel (channel_info : ChannelInfo ) -> Channel :
72- if channel_info .use_ssl :
73- return secure_channel (
74- channel_info .url_without_protocol ,
75- ssl_channel_credentials (),
76- CHANNEL_OPTIONS ,
77- compression = Compression .Gzip ,
78- )
79- return insecure_channel (channel_info .url_without_protocol , CHANNEL_OPTIONS , compression = Compression .NoCompression )
84+ match channel_info .protocol :
85+ case ChannelProtocol .HTTPS :
86+ return secure_channel (
87+ f"{ channel_info .address } :{ channel_info .port } " ,
88+ ssl_channel_credentials (),
89+ CHANNEL_OPTIONS ,
90+ compression = Compression .Gzip ,
91+ )
92+ case ChannelProtocol .HTTP :
93+ return insecure_channel (
94+ f"{ channel_info .address } :{ channel_info .port } " , CHANNEL_OPTIONS , compression = Compression .NoCompression
95+ )
96+ case ChannelProtocol .UNIX :
97+ return insecure_channel (channel_info .address , CHANNEL_OPTIONS , compression = Compression .NoCompression )
98+ case _:
99+ raise ValueError (f"Unsupported channel protocol: { channel_info .protocol } " )
80100
81101
82102_URL_SCHEME = re .compile (r"^(https?://)?([^: ]+)(:\d+)?/?$" )
@@ -98,27 +118,28 @@ def parse_channel_info(url: str) -> ChannelInfo:
98118 A ChannelInfo object that can be used to create a gRPC channel.
99119 """
100120 # See https://github.com/grpc/grpc/blob/master/doc/naming.md
101- if url .startswith ("unix:" ):
102- return ChannelInfo (url , False )
121+ if url .startswith ("unix:" ): ## unix:///absolute/path or unix://path
122+ return ChannelInfo (url , 0 , ChannelProtocol . UNIX )
103123
104124 # `urllib.parse.urlparse` behaves a bit weird with URLs that don't have a scheme but a port number, so regex it is
105125 if (match := _URL_SCHEME .match (url )) is None :
106126 raise ValueError (f"Invalid URL: { url } " )
107127 scheme , netloc , port = match .groups ()
108128 netloc = netloc .rstrip ("/" )
109- use_ssl = True
129+ protocol = ChannelProtocol . HTTPS
110130
111131 if scheme == "http://" : # explicitly set http -> require a port
112132 if port is None :
113133 raise ValueError ("Explicit port required for insecure HTTP channel" )
114- use_ssl = False
134+ protocol = ChannelProtocol .HTTP
135+
136+ # no scheme, but a port that looks like a dev port -> insecure
137+ if scheme is None and port is not None and port != ":443" :
138+ protocol = ChannelProtocol .HTTP
115139
116- if scheme is None and port is not None : # no scheme, but a port that looks like a dev port -> insecure
117- use_ssl = port == ":443"
140+ port_number = 443 if port is None else int (port .removeprefix (":" ))
118141
119- if use_ssl :
120- return ChannelInfo (netloc + (port or ":443" ), True )
121- return ChannelInfo (netloc + port , False )
142+ return ChannelInfo (netloc , port_number , protocol )
122143
123144
124145RequestType = TypeVar ("RequestType" )
0 commit comments