22import re
33from collections .abc import Callable
44from dataclasses import dataclass
5+ from enum import Enum
56from typing import TypeVar
67
78from grpc import (
4243 ("grpc.service_config" , json .dumps (_SERVICE_CONFIG )),
4344]
4445
46+ class ChannelProtocol (Enum ):
47+ HTTPS = 1
48+ HTTP = 2
49+ UNIX = 3
50+
4551
4652@dataclass
4753class ChannelInfo :
48- url_without_protocol : str
49- use_ssl : bool
54+ address : str
55+ """GRPC target address. For http(s) connections this is the host[:port] format without a scheme. For unix sockets
56+ this is the unix socket path including the `unix://` prefix for absolute paths or `unix:` for relative paths."""
57+ port : int
58+ """Port number for http(s) connections. For unix sockets this is always 0."""
59+ protocol : ChannelProtocol
60+ """The protocol to use for the channel."""
5061
5162
5263def open_channel (url : str , auth_token : str | None = None ) -> Channel :
@@ -69,14 +80,22 @@ def open_channel(url: str, auth_token: str | None = None) -> Channel:
6980
7081
7182def _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 )
83+ match channel_info .protocol :
84+ case ChannelProtocol .HTTPS :
85+ return secure_channel (
86+ f"{ channel_info .address } :{ channel_info .port } " ,
87+ ssl_channel_credentials (),
88+ CHANNEL_OPTIONS ,
89+ compression = Compression .Gzip ,
90+ )
91+ case ChannelProtocol .HTTP :
92+ return insecure_channel (
93+ f"{ channel_info .address } :{ channel_info .port } " , CHANNEL_OPTIONS , compression = Compression .NoCompression
94+ )
95+ case ChannelProtocol .UNIX :
96+ return insecure_channel (channel_info .address , CHANNEL_OPTIONS , compression = Compression .NoCompression )
97+ case _:
98+ raise ValueError (f"Unsupported channel protocol: { channel_info .protocol } " )
8099
81100
82101_URL_SCHEME = re .compile (r"^(https?://)?([^: ]+)(:\d+)?/?$" )
@@ -98,27 +117,28 @@ def parse_channel_info(url: str) -> ChannelInfo:
98117 A ChannelInfo object that can be used to create a gRPC channel.
99118 """
100119 # See https://github.com/grpc/grpc/blob/master/doc/naming.md
101- if url .startswith ("unix:" ):
102- return ChannelInfo (url , False )
120+ if url .startswith ("unix:" ): ## unix:///absolute/path or unix://path
121+ return ChannelInfo (url , 0 , ChannelProtocol . UNIX )
103122
104123 # `urllib.parse.urlparse` behaves a bit weird with URLs that don't have a scheme but a port number, so regex it is
105124 if (match := _URL_SCHEME .match (url )) is None :
106125 raise ValueError (f"Invalid URL: { url } " )
107126 scheme , netloc , port = match .groups ()
108127 netloc = netloc .rstrip ("/" )
109- use_ssl = True
128+ protocol = ChannelProtocol . HTTPS
110129
111130 if scheme == "http://" : # explicitly set http -> require a port
112131 if port is None :
113132 raise ValueError ("Explicit port required for insecure HTTP channel" )
114- use_ssl = False
133+ protocol = ChannelProtocol .HTTP
134+
135+ # no scheme, but a port that looks like a dev port -> insecure
136+ if scheme is None and port is not None and port != ":443" :
137+ protocol = ChannelProtocol .HTTP
115138
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"
139+ port_number = 443 if port is None else int (port .removeprefix (":" ))
118140
119- if use_ssl :
120- return ChannelInfo (netloc + (port or ":443" ), True )
121- return ChannelInfo (netloc + port , False )
141+ return ChannelInfo (netloc , port_number , protocol )
122142
123143
124144RequestType = TypeVar ("RequestType" )
0 commit comments