Skip to content

Commit 7581a23

Browse files
committed
support ipv6 in OSCUDPServer
1 parent 66ef6ac commit 7581a23

File tree

4 files changed

+63
-5
lines changed

4 files changed

+63
-5
lines changed

pythonosc/osc_message_builder.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,19 @@ def args(self) -> List[Tuple[str, Union[ArgValue, None]]]:
7272
"""Returns the (type, value) arguments list of this message."""
7373
return self._args
7474

75-
def _valid_type(self, arg_type: str) -> bool:
76-
if arg_type in self._SUPPORTED_ARG_TYPES:
77-
return True
75+
def _valid_type(self, arg_type: Union[str, List[Any]]) -> bool:
76+
if isinstance(arg_type, str):
77+
return arg_type in self._SUPPORTED_ARG_TYPES
7878
elif isinstance(arg_type, list):
7979
for sub_type in arg_type:
8080
if not self._valid_type(sub_type):
8181
return False
8282
return True
8383
return False
8484

85-
def add_arg(self, arg_value: ArgValue, arg_type: Optional[str] = None) -> None:
85+
def add_arg(
86+
self, arg_value: ArgValue, arg_type: Optional[Union[str, List[Any]]] = None
87+
) -> None:
8688
"""Add a typed argument to this message.
8789
8890
Args:
@@ -100,7 +102,7 @@ def add_arg(self, arg_value: ArgValue, arg_type: Optional[str] = None) -> None:
100102
arg_type = self._get_arg_type(arg_value)
101103
if isinstance(arg_type, list):
102104
self._args.append((self.ARG_TYPE_ARRAY_START, None))
103-
for v, t in zip(arg_value, arg_type): # type: ignore[arg-type, var-annotated]
105+
for v, t in zip(arg_value, arg_type): # type: ignore[arg-type]
104106
self.add_arg(v, t)
105107
self._args.append((self.ARG_TYPE_ARRAY_STOP, None))
106108
else:

pythonosc/osc_server.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import os
5+
import socket
56
import socketserver
67
from socket import socket as _socket
78
from typing import Any, Coroutine, Tuple, Union, cast
@@ -64,6 +65,7 @@ def __init__(
6465
dispatcher: Dispatcher,
6566
bind_and_activate: bool = True,
6667
timeout: float | None = None,
68+
family: socket.AddressFamily | None = None,
6769
) -> None:
6870
"""Initialize
6971
@@ -72,7 +74,25 @@ def __init__(
7274
dispatcher: Dispatcher this server will use
7375
(optional) bind_and_activate: default=True defines if the server has to start on call of constructor
7476
(optional) timeout: Default timeout in seconds for socket operations
77+
(optional) family: socket.AF_INET or socket.AF_INET6. If None, it will be inferred from server_address.
7578
"""
79+
if family is not None:
80+
self.address_family = family
81+
else:
82+
# Try to infer address family from server_address
83+
try:
84+
infos = socket.getaddrinfo(
85+
server_address[0],
86+
server_address[1],
87+
type=socket.SOCK_DGRAM,
88+
family=socket.AF_UNSPEC,
89+
)
90+
if infos:
91+
self.address_family = infos[0][0]
92+
except (socket.gaierror, IndexError):
93+
# Fallback to default if resolution fails
94+
pass
95+
7696
super().__init__(server_address, _UDPHandler, bind_and_activate)
7797
self._dispatcher = dispatcher
7898
self.timeout = timeout

pythonosc/osc_tcp_server.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import asyncio
3636
import logging
3737
import os
38+
import socket
3839
import socketserver
3940
import struct
4041
from typing import List, Tuple
@@ -146,11 +147,30 @@ def __init__(
146147
server_address: Tuple[str | bytes | bytearray, int],
147148
dispatcher: Dispatcher,
148149
mode: str = MODE_1_1,
150+
family: socket.AddressFamily | None = None,
149151
):
150152
self.request_queue_size = 300
151153
self.mode = mode
152154
if mode not in [MODE_1_0, MODE_1_1]:
153155
raise ValueError("OSC Mode must be '1.0' or '1.1'")
156+
157+
if family is not None:
158+
self.address_family = family
159+
elif isinstance(server_address[0], str):
160+
# Try to infer address family from server_address
161+
try:
162+
infos = socket.getaddrinfo(
163+
server_address[0],
164+
server_address[1],
165+
type=socket.SOCK_STREAM,
166+
family=socket.AF_UNSPEC,
167+
)
168+
if infos:
169+
self.address_family = infos[0][0]
170+
except (socket.gaierror, IndexError):
171+
# Fallback to default if resolution fails
172+
pass
173+
154174
if self.mode == MODE_1_0:
155175
super().__init__(server_address, _TCPHandler1_0)
156176
else:

pythonosc/test/test_osc_server.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import socket
12
import unittest
23
import unittest.mock
34

@@ -119,6 +120,21 @@ def test_init_timeout(self, mock_socket_ctor):
119120
server = osc_server.OSCUDPServer(("127.0.0.1", 0), dispatcher, timeout=10.0)
120121
self.assertEqual(server.timeout, 10.0)
121122

123+
@unittest.mock.patch("socket.socket")
124+
def test_init_family_inference_ipv4(self, mock_socket_ctor):
125+
dispatcher = unittest.mock.Mock()
126+
server = osc_server.OSCUDPServer(("127.0.0.1", 0), dispatcher)
127+
self.assertEqual(server.address_family, socket.AF_INET)
128+
129+
@unittest.mock.patch("socket.socket")
130+
def test_init_family_inference_ipv6(self, mock_socket_ctor):
131+
dispatcher = unittest.mock.Mock()
132+
# Mock getaddrinfo to return IPv6 for this test to be environment-independent
133+
with unittest.mock.patch("socket.getaddrinfo") as mock_getaddrinfo:
134+
mock_getaddrinfo.return_value = [(socket.AF_INET6, None, None, None, None)]
135+
server = osc_server.OSCUDPServer(("::1", 0), dispatcher)
136+
self.assertEqual(server.address_family, socket.AF_INET6)
137+
122138

123139
if __name__ == "__main__":
124140
unittest.main()

0 commit comments

Comments
 (0)