Compare commits

...

4 Commits

Author SHA1 Message Date
Chris Chua 3e221b77b4
Merge 0dfe05769e into 62f968a4c8 2024-07-31 12:32:13 +02:00
Vladimir Magamedov 62f968a4c8 Updated changelog, issued 0.4.8rc2 release candidate 2024-07-24 23:09:16 +03:00
Vladimir Magamedov e9adb679ec Fixed Channel to construct valid authority header when host is the IPv6 address, closes #197 2024-07-21 21:34:11 +03:00
chua 0dfe05769e first configurable version of adding a prefix 2024-06-28 07:55:48 +00:00
5 changed files with 51 additions and 10 deletions

View File

@ -4,6 +4,9 @@ Changelog
0.4.8 0.4.8
~~~~~ ~~~~~
- Fixed ``authority`` header for the case when Channel's ``host`` argument
is the IPv6 address
- Fixed ``Channel`` for the case when ``ssl`` module is not available
- Dropped Python 3.7 support - Dropped Python 3.7 support
- Added "wheel" packaging format - Added "wheel" packaging format

View File

@ -1,7 +1,7 @@
from .const import Status from .const import Status
from .exceptions import GRPCError from .exceptions import GRPCError
__version__ = '0.4.8rc1' __version__ = '0.4.8rc2'
__all__ = ( __all__ = (
'Status', 'Status',

View File

@ -4,6 +4,7 @@ import http
import time import time
import asyncio import asyncio
import warnings import warnings
import ipaddress
from types import TracebackType from types import TracebackType
from typing import Generic, Optional, Union, Type, List, Sequence, Any, cast from typing import Generic, Optional, Union, Type, List, Sequence, Any, cast
@ -683,9 +684,8 @@ class Channel:
self._codec = codec self._codec = codec
self._status_details_codec = status_details_codec self._status_details_codec = status_details_codec
self._ssl = ssl or None self._ssl = ssl or None
self._authority = '{}:{}'.format(self._host, self._port)
self._scheme = 'https' if self._ssl else 'http' self._scheme = 'https' if self._ssl else 'http'
self._authority = '{}:{}'.format(self._host, self._port) self._authority = self._get_authority(self._host, self._port)
self._h2_config = H2Configuration( self._h2_config = H2Configuration(
client_side=True, client_side=True,
header_encoding='ascii', header_encoding='ascii',
@ -779,6 +779,15 @@ class Channel:
ctx.set_alpn_protocols(['h2']) ctx.set_alpn_protocols(['h2'])
return ctx return ctx
def _get_authority(self, host: str, port: int) -> str:
try:
ipv6_address = ipaddress.IPv6Address(host)
except ipaddress.AddressValueError:
pass
else:
host = f"[{ipv6_address}]"
return "{}:{}".format(host, port)
def request( def request(
self, self,
name: str, name: str,

View File

@ -22,6 +22,7 @@ _CARDINALITY = {
(True, True): const.Cardinality.STREAM_STREAM, (True, True): const.Cardinality.STREAM_STREAM,
} }
_PB2_MODULE_PREFIX_PARAMETER_KEY = 'pb2_module_prefix'
class Method(NamedTuple): class Method(NamedTuple):
name: str name: str
@ -174,8 +175,9 @@ def _base_module_name(proto_file_path: str) -> str:
return basename.replace("-", "_").replace("/", ".") return basename.replace("-", "_").replace("/", ".")
_pb2_module_prefix: str = ''
def _proto2pb2_module_name(proto_file_path: str) -> str: def _proto2pb2_module_name(proto_file_path: str) -> str:
return _base_module_name(proto_file_path) + "_pb2" return _pb2_module_prefix + _base_module_name(proto_file_path) + "_pb2"
def _proto2grpc_module_name(proto_file_path: str) -> str: def _proto2grpc_module_name(proto_file_path: str) -> str:
@ -208,10 +210,19 @@ def _type_names(
parents.pop() parents.pop()
def _populate_config(request_parameter: str):
global _pb2_module_prefix
config = dict(item.split("=") for item in request_parameter.split(','))
if config.get(_PB2_MODULE_PREFIX_PARAMETER_KEY):
_pb2_module_prefix = config[_PB2_MODULE_PREFIX_PARAMETER_KEY]
def main() -> None: def main() -> None:
with os.fdopen(sys.stdin.fileno(), 'rb') as inp: with os.fdopen(sys.stdin.fileno(), 'rb') as inp:
request = CodeGeneratorRequest.FromString(inp.read()) request = CodeGeneratorRequest.FromString(inp.read())
_populate_config(request.parameter)
types_map: Dict[str, str] = {} types_map: Dict[str, str] = {}
for pf in request.proto_file: for pf in request.proto_file:
for mt in pf.message_type: for mt in pf.message_type:

View File

@ -1,6 +1,7 @@
import os import os
import socket import socket
import tempfile import tempfile
import ipaddress
import pytest import pytest
@ -46,19 +47,27 @@ class ClientServer:
channel = None channel = None
channel_ctx = None channel_ctx = None
def __init__(self, *, host="127.0.0.1"):
self.host = host
async def __aenter__(self): async def __aenter__(self):
host = '127.0.0.1' try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: ipaddress.IPv6Address(self.host)
s.bind(('127.0.0.1', 0)) except ipaddress.AddressValueError:
_, port = s.getsockname() family = socket.AF_INET
else:
family = socket.AF_INET6
with socket.socket(family, socket.SOCK_STREAM) as s:
s.bind((self.host, 0))
_, port, *_ = s.getsockname()
dummy_service = DummyService() dummy_service = DummyService()
self.server = Server([dummy_service]) self.server = Server([dummy_service])
await self.server.start(host, port) await self.server.start(self.host, port)
self.server_ctx = await self.server.__aenter__() self.server_ctx = await self.server.__aenter__()
self.channel = Channel(host=host, port=port) self.channel = Channel(host=self.host, port=port)
self.channel_ctx = await self.channel.__aenter__() self.channel_ctx = await self.channel.__aenter__()
dummy_stub = DummyServiceStub(self.channel) dummy_stub = DummyServiceStub(self.channel)
return dummy_service, dummy_stub return dummy_service, dummy_stub
@ -211,3 +220,12 @@ async def test_stream_stream_advanced():
assert await stream.recv_message() == DummyReply(value='baz') assert await stream.recv_message() == DummyReply(value='baz')
assert await stream.recv_message() is None assert await stream.recv_message() is None
@pytest.mark.asyncio
@pytest.mark.skipif(not socket.has_ipv6, reason="No IPv6 support")
async def test_ipv6():
async with ClientServer(host="::1") as (handler, stub):
reply = await stub.UnaryUnary(DummyRequest(value='ping'))
assert reply == DummyReply(value='pong')
assert handler.log == [DummyRequest(value='ping')]