python-osc 1.9.2__tar.gz → 1.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {python_osc-1.9.2/python_osc.egg-info → python_osc-1.10.0}/PKG-INFO +28 -29
  2. {python_osc-1.9.2 → python_osc-1.10.0}/pyproject.toml +16 -3
  3. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/dispatcher.py +51 -18
  4. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_bundle_builder.py +4 -2
  5. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_message.py +6 -2
  6. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_message_builder.py +5 -5
  7. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_packet.py +1 -2
  8. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_server.py +1 -2
  9. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_tcp_server.py +4 -4
  10. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/parsing/osc_types.py +12 -18
  11. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/tcp_client.py +19 -0
  12. python_osc-1.10.0/pythonosc/test/parsing/__init__.py +0 -0
  13. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/parsing/test_osc_types.py +5 -5
  14. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_bundle.py +4 -10
  15. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_message.py +4 -12
  16. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_message_builder.py +1 -1
  17. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_packet.py +1 -1
  18. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_server.py +1 -1
  19. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_tcp_server.py +9 -9
  20. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_udp_client.py +16 -0
  21. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/udp_client.py +12 -1
  22. python_osc-1.9.2/MANIFEST.in +0 -3
  23. python_osc-1.9.2/PKG-INFO +0 -198
  24. python_osc-1.9.2/python_osc.egg-info/SOURCES.txt +0 -37
  25. python_osc-1.9.2/python_osc.egg-info/dependency_links.txt +0 -1
  26. python_osc-1.9.2/python_osc.egg-info/top_level.txt +0 -1
  27. python_osc-1.9.2/setup.cfg +0 -4
  28. {python_osc-1.9.2 → python_osc-1.10.0}/LICENSE.txt +0 -0
  29. {python_osc-1.9.2 → python_osc-1.10.0}/README.rst +0 -0
  30. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/__init__.py +0 -0
  31. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/osc_bundle.py +0 -0
  32. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/parsing/__init__.py +0 -0
  33. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/parsing/ntp.py +0 -0
  34. /python_osc-1.9.2/pythonosc/test/__init__.py → /python_osc-1.10.0/pythonosc/py.typed +0 -0
  35. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/slip.py +0 -0
  36. {python_osc-1.9.2/pythonosc/test/parsing → python_osc-1.10.0/pythonosc/test}/__init__.py +0 -0
  37. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/parsing/test_ntp.py +0 -0
  38. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_dispatcher.py +0 -0
  39. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_osc_bundle_builder.py +0 -0
  40. {python_osc-1.9.2 → python_osc-1.10.0}/pythonosc/test/test_tcp_client.py +0 -0
@@ -1,35 +1,34 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: python-osc
3
- Version: 1.9.2
3
+ Version: 1.10.0
4
4
  Summary: Open Sound Control server and client implementations in pure Python
5
+ Keywords: osc,sound,midi,music
6
+ Author: attwad
5
7
  Author-email: attwad <tmusoft@gmail.com>
6
8
  License: This is free and unencumbered software released into the public domain.
7
-
8
- Anyone is free to copy, modify, publish, use, compile, sell, or
9
- distribute this software, either in source code form or as a compiled
10
- binary, for any purpose, commercial or non-commercial, and by any
11
- means.
12
-
13
- In jurisdictions that recognize copyright laws, the author or authors
14
- of this software dedicate any and all copyright interest in the
15
- software to the public domain. We make this dedication for the benefit
16
- of the public at large and to the detriment of our heirs and
17
- successors. We intend this dedication to be an overt act of
18
- relinquishment in perpetuity of all present and future rights to this
19
- software under copyright law.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
- OTHER DEALINGS IN THE SOFTWARE.
28
-
29
- For more information, please refer to <http://unlicense.org/>
30
-
31
- Project-URL: Repository, https://github.com/attwad/python-osc
32
- Keywords: osc,sound,midi,music
9
+
10
+ Anyone is free to copy, modify, publish, use, compile, sell, or
11
+ distribute this software, either in source code form or as a compiled
12
+ binary, for any purpose, commercial or non-commercial, and by any
13
+ means.
14
+
15
+ In jurisdictions that recognize copyright laws, the author or authors
16
+ of this software dedicate any and all copyright interest in the
17
+ software to the public domain. We make this dedication for the benefit
18
+ of the public at large and to the detriment of our heirs and
19
+ successors. We intend this dedication to be an overt act of
20
+ relinquishment in perpetuity of all present and future rights to this
21
+ software under copyright law.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
27
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
28
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29
+ OTHER DEALINGS IN THE SOFTWARE.
30
+
31
+ For more information, please refer to <http://unlicense.org/>
33
32
  Classifier: Development Status :: 5 - Production/Stable
34
33
  Classifier: Intended Audience :: Developers
35
34
  Classifier: License :: Freely Distributable
@@ -37,8 +36,8 @@ Classifier: Programming Language :: Python :: 3
37
36
  Classifier: Topic :: Multimedia :: Sound/Audio
38
37
  Classifier: Topic :: System :: Networking
39
38
  Requires-Python: >=3.10
39
+ Project-URL: Repository, https://github.com/attwad/python-osc
40
40
  Description-Content-Type: text/x-rst
41
- License-File: LICENSE.txt
42
41
 
43
42
  ==========
44
43
  python-osc
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools"]
3
- build-backend = "setuptools.build_meta"
2
+ requires = ["uv-build"]
3
+ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "python-osc"
7
- version = "1.9.2"
7
+ version = "1.10.0"
8
8
  description = "Open Sound Control server and client implementations in pure Python"
9
9
  readme = "README.rst"
10
10
  requires-python = ">=3.10"
@@ -21,10 +21,23 @@ classifiers = [
21
21
  "Topic :: Multimedia :: Sound/Audio",
22
22
  "Topic :: System :: Networking",
23
23
  ]
24
+ dependencies = []
24
25
 
25
26
  [project.urls]
26
27
  Repository = "https://github.com/attwad/python-osc"
27
28
 
29
+ [dependency-groups]
30
+ dev = [
31
+ "pytest",
32
+ "mypy",
33
+ "ruff",
34
+ "pytest-cov",
35
+ ]
36
+
37
+ [tool.uv.build-backend]
38
+ module-name = "pythonosc"
39
+ module-root = "."
40
+
28
41
  [tool.mypy]
29
42
  # Would be great to turn this on, however there's too many cases it would break
30
43
  # right now.
@@ -1,5 +1,4 @@
1
- """Maps OSC addresses to handler functions
2
- """
1
+ """Maps OSC addresses to handler functions"""
3
2
 
4
3
  import collections
5
4
  import inspect
@@ -183,28 +182,62 @@ class Dispatcher(object):
183
182
  ) -> Generator[Handler, None, None]:
184
183
  """Yields handlers matching an address
185
184
 
186
-
187
185
  Args:
188
186
  address_pattern: Address to match
189
187
 
190
188
  Returns:
191
189
  Generator yielding Handlers matching address_pattern
192
190
  """
193
- # First convert the address_pattern into a matchable regexp.
194
- # '?' in the OSC Address Pattern matches any single character.
195
- # Let's consider numbers and _ "characters" too here, it's not said
196
- # explicitly in the specification but it sounds good.
197
- escaped_address_pattern = re.escape(address_pattern)
198
- pattern = escaped_address_pattern.replace("\\?", "\\w?")
199
- # '*' in the OSC Address Pattern matches any sequence of zero or more
200
- # characters.
201
- pattern = pattern.replace("\\*", "[\\w|\\+]*")
202
- # The rest of the syntax in the specification is like the re module so
203
- # we're fine.
204
- pattern = f"{pattern}$"
205
- patterncompiled = re.compile(pattern)
206
- matched = False
191
+ # Convert OSC Address Pattern to a Python regular expression.
192
+ # Spec: https://opensoundcontrol.stanford.edu/spec-1_0.html#osc-address-patterns
193
+
194
+ pattern = "^"
195
+ i = 0
196
+ while i < len(address_pattern):
197
+ c = address_pattern[i]
198
+ if c == "*":
199
+ pattern += "[^/]*"
200
+ elif c == "?":
201
+ pattern += "[^/]"
202
+ elif c == "[":
203
+ pattern += "["
204
+ i += 1
205
+ if i < len(address_pattern) and address_pattern[i] == "!":
206
+ pattern += "^"
207
+ i += 1
208
+ while i < len(address_pattern) and address_pattern[i] != "]":
209
+ if address_pattern[i] in r"\^$.|()+*?":
210
+ pattern += "\\"
211
+ pattern += address_pattern[i]
212
+ i += 1
213
+ pattern += "]"
214
+ elif c == "{":
215
+ pattern += "("
216
+ i += 1
217
+ while i < len(address_pattern) and address_pattern[i] != "}":
218
+ char = address_pattern[i]
219
+ if char == ",":
220
+ pattern += "|"
221
+ elif char in r"\^$.|()[]+*?":
222
+ pattern += "\\" + char
223
+ else:
224
+ pattern += char
225
+ i += 1
226
+ pattern += ")"
227
+ elif c in r"\^$.|()[]+?":
228
+ pattern += "\\" + c
229
+ else:
230
+ pattern += c
231
+ i += 1
232
+ pattern += "$"
207
233
 
234
+ try:
235
+ patterncompiled = re.compile(pattern)
236
+ except re.error:
237
+ # If the pattern is invalid, it won't match anything.
238
+ return
239
+
240
+ matched = False
208
241
  for addr, handlers in self._map.items():
209
242
  if patterncompiled.match(addr) or (
210
243
  ("*" in addr)
@@ -317,7 +350,7 @@ class Dispatcher(object):
317
350
  )
318
351
  if result:
319
352
  results.append(result)
320
- except osc_packet.ParseError as e:
353
+ except osc_packet.ParseError:
321
354
  pass
322
355
  return results
323
356
 
@@ -25,9 +25,11 @@ class OscBundleBuilder(object):
25
25
  seconds since the epoch in UTC or IMMEDIATELY.
26
26
  """
27
27
  self._timestamp = timestamp
28
- self._contents: List[osc_bundle.OscBundle] = []
28
+ self._contents: List[osc_bundle.OscBundle | osc_message.OscMessage] = []
29
29
 
30
- def add_content(self, content: osc_bundle.OscBundle) -> None:
30
+ def add_content(
31
+ self, content: osc_bundle.OscBundle | osc_message.OscMessage
32
+ ) -> None:
31
33
  """Add a new content to this bundle.
32
34
 
33
35
  Args:
@@ -34,8 +34,12 @@ class OscMessage(object):
34
34
 
35
35
  # Get the parameters types.
36
36
  type_tag, index = osc_types.get_string(self._dgram, index)
37
- if type_tag.startswith(","):
38
- type_tag = type_tag[1:]
37
+ if not type_tag.startswith(","):
38
+ raise ParseError(
39
+ f"OSC Type Tag String must start with a comma, got: {type_tag}"
40
+ )
41
+
42
+ type_tag = type_tag[1:]
39
43
 
40
44
  params = [] # type: List[Any]
41
45
  param_stack = [params]
@@ -5,7 +5,7 @@ from typing import Any, Iterable, List, Optional, Tuple, Union
5
5
  from pythonosc import osc_message
6
6
  from pythonosc.parsing import osc_types
7
7
 
8
- ArgValue = Union[str, bytes, bool, int, float, osc_types.MidiPacket, list]
8
+ ArgValue = Union[str, bytes, bool, int, float, osc_types.MidiPacket, list, None]
9
9
 
10
10
 
11
11
  class BuildError(Exception):
@@ -96,7 +96,7 @@ class OscMessageBuilder(object):
96
96
  arg_type = self._get_arg_type(arg_value)
97
97
  if isinstance(arg_type, list):
98
98
  self._args.append((self.ARG_TYPE_ARRAY_START, None))
99
- for v, t in zip(arg_value, arg_type): # type: ignore[var-annotated, arg-type]
99
+ for v, t in zip(arg_value, arg_type): # type: ignore[arg-type]
100
100
  self.add_arg(v, t)
101
101
  self._args.append((self.ARG_TYPE_ARRAY_STOP, None))
102
102
  else:
@@ -121,7 +121,7 @@ class OscMessageBuilder(object):
121
121
  elif arg_value is False:
122
122
  arg_type = self.ARG_TYPE_FALSE
123
123
  elif isinstance(arg_value, int):
124
- if arg_value.bit_length() > 32:
124
+ if arg_value.bit_length() > 31:
125
125
  arg_type = self.ARG_TYPE_INT64
126
126
  else:
127
127
  arg_type = self.ARG_TYPE_INT
@@ -134,7 +134,7 @@ class OscMessageBuilder(object):
134
134
  elif arg_value is None:
135
135
  arg_type = self.ARG_TYPE_NIL
136
136
  else:
137
- raise ValueError("Infered arg_value type is not supported")
137
+ raise ValueError("Inferred arg_value type is not supported")
138
138
  return arg_type
139
139
 
140
140
  def build(self) -> osc_message.OscMessage:
@@ -195,7 +195,7 @@ class OscMessageBuilder(object):
195
195
 
196
196
  def build_msg(address: str, value: ArgValue = "") -> osc_message.OscMessage:
197
197
  builder = OscMessageBuilder(address=address)
198
- values: ArgValue
198
+ values: Iterable[Any]
199
199
  if value == "":
200
200
  values = []
201
201
  elif not isinstance(value, Iterable) or isinstance(value, (str, bytes)):
@@ -72,8 +72,7 @@ class OscPacket(object):
72
72
  else:
73
73
  # Empty packet, should not happen as per the spec but heh, UDP...
74
74
  raise ParseError(
75
- "OSC Packet should at least contain an OscMessage or an "
76
- "OscBundle."
75
+ "OSC Packet should at least contain an OscMessage or an OscBundle."
77
76
  )
78
77
  except (osc_bundle.ParseError, osc_message.ParseError) as pe:
79
78
  raise ParseError(f"Could not parse packet {pe}")
@@ -1,5 +1,4 @@
1
- """OSC Servers that receive UDP packets and invoke handlers accordingly.
2
- """
1
+ """OSC Servers that receive UDP packets and invoke handlers accordingly."""
3
2
 
4
3
  import asyncio
5
4
  import os
@@ -75,7 +75,7 @@ class _TCPHandler1_0(socketserver.BaseRequestHandler):
75
75
  )
76
76
  # resp = _call_handlers_for_packet(data, self.server.dispatcher)
77
77
  for r in resp:
78
- if not isinstance(r, list):
78
+ if not isinstance(r, tuple):
79
79
  r = [r]
80
80
  msg = osc_message_builder.build_msg(r[0], r[1:])
81
81
  b = struct.pack("!I", len(msg.dgram))
@@ -117,7 +117,7 @@ class _TCPHandler1_1(socketserver.BaseRequestHandler):
117
117
  p, self.client_address
118
118
  )
119
119
  for r in resp:
120
- if not isinstance(r, list):
120
+ if not isinstance(r, tuple):
121
121
  r = [r]
122
122
  msg = osc_message_builder.build_msg(r[0], r[1:])
123
123
  self.request.sendall(slip.encode(msg.dgram))
@@ -284,7 +284,7 @@ class AsyncOSCTCPServer:
284
284
  buf, client_address
285
285
  )
286
286
  for r in result:
287
- if not isinstance(r, list):
287
+ if not isinstance(r, tuple):
288
288
  r = [r]
289
289
  msg = osc_message_builder.build_msg(r[0], r[1:])
290
290
  b = struct.pack("!I", len(msg.dgram))
@@ -319,7 +319,7 @@ class AsyncOSCTCPServer:
319
319
  p, client_address
320
320
  )
321
321
  for r in result:
322
- if not isinstance(r, list):
322
+ if not isinstance(r, tuple):
323
323
  r = [r]
324
324
  msg = osc_message_builder.build_msg(r[0], r[1:])
325
325
  writer.write(slip.encode(msg.dgram))
@@ -71,24 +71,21 @@ def get_string(dgram: bytes, start_index: int) -> Tuple[str, int]:
71
71
  raise ParseError("start_index < 0")
72
72
  offset = 0
73
73
  try:
74
- if (
75
- len(dgram) > start_index + _STRING_DGRAM_PAD
76
- and dgram[start_index + _STRING_DGRAM_PAD] == _EMPTY_STR_DGRAM
77
- ):
78
- return "", start_index + _STRING_DGRAM_PAD
79
74
  while dgram[start_index + offset] != 0:
80
75
  offset += 1
81
- # Align to a byte word.
82
- if (offset) % _STRING_DGRAM_PAD == 0:
83
- offset += _STRING_DGRAM_PAD
84
- else:
85
- offset += -offset % _STRING_DGRAM_PAD
86
- # Python slices do not raise an IndexError past the last index,
87
- # do it ourselves.
88
- if offset > len(dgram[start_index:]):
76
+
77
+ # OSC spec: "followed by a null, followed by 0-3 additional null characters
78
+ # to make the total number of bits a multiple of 32"
79
+ # This means the total length (including the first null) must be a multiple of 4.
80
+ total_len = offset + 1
81
+ if total_len % 4 != 0:
82
+ total_len += 4 - (total_len % 4)
83
+
84
+ if start_index + total_len > len(dgram):
89
85
  raise ParseError("Datagram is too short")
86
+
90
87
  data_str = dgram[start_index : start_index + offset]
91
- return data_str.replace(b"\x00", b"").decode("utf-8"), start_index + offset
88
+ return data_str.decode("utf-8"), start_index + total_len
92
89
  except IndexError as ie:
93
90
  raise ParseError(f"Could not parse datagram {ie}")
94
91
  except TypeError as te:
@@ -214,11 +211,8 @@ def get_timetag(dgram: bytes, start_index: int) -> Tuple[Tuple[datetime, int], i
214
211
  timetag, _ = get_uint64(dgram, start_index)
215
212
  seconds, fraction = ntp.parse_timestamp(timetag)
216
213
 
217
- hours, seconds = seconds // 3600, seconds % 3600
218
- minutes, seconds = seconds // 60, seconds % 60
219
-
220
214
  utc = datetime.combine(ntp._NTP_EPOCH, datetime.min.time()) + timedelta(
221
- hours=hours, minutes=minutes, seconds=seconds
215
+ seconds=seconds
222
216
  )
223
217
 
224
218
  return (utc, fraction), start_index + _TIMETAG_DGRAM_LEN
@@ -122,6 +122,25 @@ class SimpleTCPClient(TCPClient):
122
122
  r = self.receive(timeout)
123
123
 
124
124
 
125
+ class TCPDispatchClient(SimpleTCPClient):
126
+ """OSC TCP Client that includes a :class:`Dispatcher` for handling responses and other messages from the server"""
127
+
128
+ dispatcher = Dispatcher()
129
+
130
+ def handle_messages(self, timeout_sec: int = 30) -> None:
131
+ """Wait :int:`timeout` seconds for a message from the server and process each message with the registered
132
+ handlers. Continue until a timeout occurs.
133
+
134
+ Args:
135
+ timeout: Time in seconds to wait for a message
136
+ """
137
+ r = self.receive(timeout_sec)
138
+ while r:
139
+ for m in r:
140
+ self.dispatcher.call_handlers_for_packet(m, (self.address, self.port))
141
+ r = self.receive(timeout_sec)
142
+
143
+
125
144
  class AsyncTCPClient:
126
145
  """Async OSC client to send :class:`OscMessage` or :class:`OscBundle` via TCP"""
127
146
 
File without changes
@@ -85,10 +85,10 @@ class TestRGBA(unittest.TestCase):
85
85
  b"\x00\x00\x00\x01": (1, 4),
86
86
  b"\x00\x00\x00\x02": (2, 4),
87
87
  b"\x00\x00\x00\x03": (3, 4),
88
- b"\xFF\x00\x00\x00": (4278190080, 4),
89
- b"\x00\xFF\x00\x00": (16711680, 4),
90
- b"\x00\x00\xFF\x00": (65280, 4),
91
- b"\x00\x00\x00\xFF": (255, 4),
88
+ b"\xff\x00\x00\x00": (4278190080, 4),
89
+ b"\x00\xff\x00\x00": (16711680, 4),
90
+ b"\x00\x00\xff\x00": (65280, 4),
91
+ b"\x00\x00\x00\xff": (255, 4),
92
92
  b"\x00\x00\x00\x01GARBAGE": (1, 4),
93
93
  }
94
94
 
@@ -164,7 +164,7 @@ class TestDate(unittest.TestCase):
164
164
  (datetime(1900, 1, 1, 0, 0, 0), 0),
165
165
  8,
166
166
  ),
167
- b"\x83\xaa\x7E\x80\x0A\x00\xB0\x0C": (
167
+ b"\x83\xaa\x7e\x80\x0a\x00\xb0\x0c": (
168
168
  (datetime(1970, 1, 1, 0, 0, 0), 167817228),
169
169
  8,
170
170
  ),
@@ -46,7 +46,7 @@ _DGRAM_TWO_MESSAGES_IN_BUNDLE = (
46
46
  b"?\x00\x00\x00"
47
47
  )
48
48
 
49
- _DGRAM_EMPTY_BUNDLE = b"#bundle\x00" b"\x00\x00\x00\x00\x00\x00\x00\x01"
49
+ _DGRAM_EMPTY_BUNDLE = b"#bundle\x00\x00\x00\x00\x00\x00\x00\x00\x01"
50
50
 
51
51
  _DGRAM_BUNDLE_IN_BUNDLE = (
52
52
  b"#bundle\x00"
@@ -60,20 +60,14 @@ _DGRAM_BUNDLE_IN_BUNDLE = (
60
60
  b"?\x00\x00\x00"
61
61
  )
62
62
 
63
- _DGRAM_INVALID = b"#bundle\x00" b"\x00\x00\x00"
63
+ _DGRAM_INVALID = b"#bundle\x00\x00\x00\x00"
64
64
 
65
65
  _DGRAM_INVALID_INDEX = (
66
- b"#bundle\x00"
67
- b"\x00\x00\x00\x00\x00\x00\x00\x01"
68
- b"\x00\x00\x00\x20"
69
- b"/SYNC\x00\x00\x00\x00"
66
+ b"#bundle\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x20/SYNC\x00\x00\x00\x00"
70
67
  )
71
68
 
72
69
  _DGRAM_UNKNOWN_TYPE = (
73
- b"#bundle\x00"
74
- b"\x00\x00\x00\x00\x00\x00\x00\x01"
75
- b"\x00\x00\x00\x10"
76
- b"iamnotaslash"
70
+ b"#bundle\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x10iamnotaslash"
77
71
  )
78
72
 
79
73
 
@@ -5,11 +5,11 @@ from pythonosc import osc_message
5
5
  from datetime import datetime
6
6
 
7
7
  # Datagrams sent by Reaktor 5.8 by Native Instruments (c).
8
- _DGRAM_KNOB_ROTATES = b"/FB\x00" b",f\x00\x00" b">xca=q"
8
+ _DGRAM_KNOB_ROTATES = b"/FB\x00,f\x00\x00>xca=q"
9
9
 
10
- _DGRAM_SWITCH_GOES_OFF = b"/SYNC\x00\x00\x00" b",f\x00\x00" b"\x00\x00\x00\x00"
10
+ _DGRAM_SWITCH_GOES_OFF = b"/SYNC\x00\x00\x00,f\x00\x00\x00\x00\x00\x00"
11
11
 
12
- _DGRAM_SWITCH_GOES_ON = b"/SYNC\x00\x00\x00" b",f\x00\x00" b"?\x00\x00\x00"
12
+ _DGRAM_SWITCH_GOES_ON = b"/SYNC\x00\x00\x00,f\x00\x00?\x00\x00\x00"
13
13
 
14
14
  _DGRAM_NO_PARAMS = b"/SYNC\x00\x00\x00"
15
15
 
@@ -22,15 +22,7 @@ _DGRAM_ALL_STANDARD_TYPES_OF_PARAMS = (
22
22
  b"\x00\x00\x00\x08stuff\x00\x00\x00"
23
23
  ) # b"stuff\x00\x00\x00"
24
24
 
25
- _DGRAM_ALL_NON_STANDARD_TYPES_OF_PARAMS = (
26
- b"/SYNC\x00\x00\x00"
27
- b"T" # True
28
- b"F" # False
29
- b"N" # Nil
30
- b"[]th\x00" # Empty array
31
- b"\x00\x00\x00\x00\x00\x00\x00\x00"
32
- b"\x00\x00\x00\xe8\xd4\xa5\x10\x00" # 1000000000000
33
- )
25
+ _DGRAM_ALL_NON_STANDARD_TYPES_OF_PARAMS = b"/SYNC\x00\x00\x00,TFN[]th\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe8\xd4\xa5\x10\x00"
34
26
 
35
27
  _DGRAM_COMPLEX_ARRAY_PARAMS = (
36
28
  b"/SYNC\x00\x00\x00"
@@ -19,7 +19,7 @@ class TestOscMessageBuilder(unittest.TestCase):
19
19
  builder = osc_message_builder.OscMessageBuilder("")
20
20
  self.assertRaises(ValueError, builder.add_arg, "what?", 1)
21
21
 
22
- def test_add_arg_invalid_infered_type(self):
22
+ def test_add_arg_invalid_inferred_type(self):
23
23
  builder = osc_message_builder.OscMessageBuilder("")
24
24
  self.assertRaises(ValueError, builder.add_arg, {"name": "John"})
25
25
 
@@ -17,7 +17,7 @@ _DGRAM_TWO_MESSAGES_IN_BUNDLE = (
17
17
  b"?\x00\x00\x00"
18
18
  )
19
19
 
20
- _DGRAM_EMPTY_BUNDLE = b"#bundle\x00" b"\x00\x00\x00\x00\x00\x00\x00\x01"
20
+ _DGRAM_EMPTY_BUNDLE = b"#bundle\x00\x00\x00\x00\x00\x00\x00\x00\x01"
21
21
 
22
22
  _DGRAM_NESTED_MESS = (
23
23
  b"#bundle\x00"
@@ -3,7 +3,7 @@ import unittest.mock
3
3
 
4
4
  from pythonosc import dispatcher, osc_server
5
5
 
6
- _SIMPLE_PARAM_INT_MSG = b"/SYNC\x00\x00\x00" b",i\x00\x00" b"\x00\x00\x00\x04"
6
+ _SIMPLE_PARAM_INT_MSG = b"/SYNC\x00\x00\x00,i\x00\x00\x00\x00\x00\x04"
7
7
 
8
8
  # Regression test for a datagram that should NOT be stripped, ever...
9
9
  _SIMPLE_PARAM_INT_9 = b"/debug\x00\x00,i\x00\x00\x00\x00\x00\t"
@@ -5,7 +5,7 @@ import unittest.mock as mock
5
5
  from pythonosc import dispatcher, osc_tcp_server
6
6
  from pythonosc.slip import END
7
7
 
8
- _SIMPLE_PARAM_INT_MSG = b"/SYNC\x00\x00\x00" b",i\x00\x00" b"\x00\x00\x00\x04"
8
+ _SIMPLE_PARAM_INT_MSG = b"/SYNC\x00\x00\x00,i\x00\x00\x00\x00\x00\x04"
9
9
 
10
10
  LEN_SIMPLE_PARAM_INT_MSG = struct.pack("!I", len(_SIMPLE_PARAM_INT_MSG))
11
11
  _SIMPLE_PARAM_INT_MSG_1_1 = END + _SIMPLE_PARAM_INT_MSG + END
@@ -95,12 +95,12 @@ class TestTCP_1_1_Handler(unittest.TestCase):
95
95
 
96
96
  def test_response_with_args(self):
97
97
  def respond(*args, **kwargs):
98
- return [
98
+ return (
99
99
  "/SYNC",
100
100
  1,
101
101
  "2",
102
102
  3.0,
103
- ]
103
+ )
104
104
 
105
105
  self.dispatcher.map("/SYNC", respond)
106
106
  mock_sock = mock.Mock()
@@ -208,12 +208,12 @@ class TestTCP_1_0_Handler(unittest.TestCase):
208
208
 
209
209
  def test_response_with_args(self):
210
210
  def respond(*args, **kwargs):
211
- return [
211
+ return (
212
212
  "/SYNC",
213
213
  1,
214
214
  "2",
215
215
  3.0,
216
- ]
216
+ )
217
217
 
218
218
  self.dispatcher.map("/SYNC", respond)
219
219
  mock_sock = mock.Mock()
@@ -314,12 +314,12 @@ class TestAsync1_1Handler(unittest.IsolatedAsyncioTestCase):
314
314
 
315
315
  async def test_response_with_args(self):
316
316
  def respond(*args, **kwargs):
317
- return [
317
+ return (
318
318
  "/SYNC",
319
319
  1,
320
320
  "2",
321
321
  3.0,
322
- ]
322
+ )
323
323
 
324
324
  self.dispatcher.map("/SYNC", respond)
325
325
  self.mock_reader.read.side_effect = [_SIMPLE_MSG_NO_PARAMS_1_1, b""]
@@ -332,12 +332,12 @@ class TestAsync1_1Handler(unittest.IsolatedAsyncioTestCase):
332
332
 
333
333
  async def test_async_response_with_args(self):
334
334
  async def respond(*args, **kwargs):
335
- return [
335
+ return (
336
336
  "/SYNC",
337
337
  1,
338
338
  "2",
339
339
  3.0,
340
- ]
340
+ )
341
341
 
342
342
  self.dispatcher.map("/SYNC", respond)
343
343
  self.mock_reader.read.side_effect = [_SIMPLE_MSG_NO_PARAMS_1_1, b""]
@@ -48,5 +48,21 @@ class TestSimpleUdpClient(unittest.TestCase):
48
48
  self.assertEqual(self.builder.add_arg.call_count, 3)
49
49
 
50
50
 
51
+ class TestUdpClientClose(unittest.TestCase):
52
+ @mock.patch("socket.socket")
53
+ def test_close(self, mock_socket_ctor):
54
+ mock_socket = mock_socket_ctor.return_value
55
+ client = udp_client.UDPClient("::1", 31337)
56
+ client.close()
57
+ self.assertTrue(mock_socket.close.called)
58
+
59
+ @mock.patch("socket.socket")
60
+ def test_context_manager(self, mock_socket_ctor):
61
+ mock_socket = mock_socket_ctor.return_value
62
+ with udp_client.UDPClient("::1", 31337) as client:
63
+ self.assertIsInstance(client, udp_client.UDPClient)
64
+ self.assertTrue(mock_socket.close.called)
65
+
66
+
51
67
  if __name__ == "__main__":
52
68
  unittest.main()
@@ -55,6 +55,18 @@ class UDPClient(object):
55
55
  self._address = address
56
56
  self._port = port
57
57
 
58
+ def __enter__(self) -> "UDPClient":
59
+ return self
60
+
61
+ def __exit__(
62
+ self, exc_type: type | None, exc_val: Exception | None, exc_tb: object | None
63
+ ) -> None:
64
+ self.close()
65
+
66
+ def close(self) -> None:
67
+ """Close the socket"""
68
+ self._sock.close()
69
+
58
70
  def send(self, content: Union[OscMessage, OscBundle]) -> None:
59
71
  """Sends an :class:`OscMessage` or :class:`OscBundle` via UDP
60
72
 
@@ -87,7 +99,6 @@ class SimpleUDPClient(UDPClient):
87
99
  value: One or more arguments to be added to the message
88
100
  """
89
101
  builder = OscMessageBuilder(address=address)
90
- values: ArgValue
91
102
  if value is None:
92
103
  pass
93
104
  elif not isinstance(value, Iterable) or isinstance(value, (str, bytes)):
@@ -1,3 +0,0 @@
1
- include README.rst
2
- include LICENSE.txt
3
- include pythonosc/py.typed
python_osc-1.9.2/PKG-INFO DELETED
@@ -1,198 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: python-osc
3
- Version: 1.9.2
4
- Summary: Open Sound Control server and client implementations in pure Python
5
- Author-email: attwad <tmusoft@gmail.com>
6
- License: This is free and unencumbered software released into the public domain.
7
-
8
- Anyone is free to copy, modify, publish, use, compile, sell, or
9
- distribute this software, either in source code form or as a compiled
10
- binary, for any purpose, commercial or non-commercial, and by any
11
- means.
12
-
13
- In jurisdictions that recognize copyright laws, the author or authors
14
- of this software dedicate any and all copyright interest in the
15
- software to the public domain. We make this dedication for the benefit
16
- of the public at large and to the detriment of our heirs and
17
- successors. We intend this dedication to be an overt act of
18
- relinquishment in perpetuity of all present and future rights to this
19
- software under copyright law.
20
-
21
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
- OTHER DEALINGS IN THE SOFTWARE.
28
-
29
- For more information, please refer to <http://unlicense.org/>
30
-
31
- Project-URL: Repository, https://github.com/attwad/python-osc
32
- Keywords: osc,sound,midi,music
33
- Classifier: Development Status :: 5 - Production/Stable
34
- Classifier: Intended Audience :: Developers
35
- Classifier: License :: Freely Distributable
36
- Classifier: Programming Language :: Python :: 3
37
- Classifier: Topic :: Multimedia :: Sound/Audio
38
- Classifier: Topic :: System :: Networking
39
- Requires-Python: >=3.10
40
- Description-Content-Type: text/x-rst
41
- License-File: LICENSE.txt
42
-
43
- ==========
44
- python-osc
45
- ==========
46
-
47
- Open Sound Control server and client implementations in **pure python**.
48
-
49
- .. image:: https://github.com/attwad/python-osc/actions/workflows/python-test.yml/badge.svg
50
- :target: https://github.com/attwad/python-osc/actions/workflows/python-test.yml
51
-
52
-
53
- Current status
54
- ==============
55
-
56
- This library was developed following the
57
- `OpenSoundControl Specification 1.0 <https://opensoundcontrol.stanford.edu/spec-1_0.html>`_
58
- and is currently in a stable state.
59
-
60
- Features
61
- ========
62
-
63
- * UDP and TCP blocking/threading/forking/asyncio server implementations
64
- * UDP and TCP clients, including asyncio support
65
- * TCP support for 1.0 and 1.1 protocol formats
66
- * int, int64, float, string, double, MIDI, timestamps, blob, nil OSC arguments
67
- * simple OSC address<->callback matching system
68
- * support for sending responses from callback handlers in client and server
69
- * extensive unit test coverage
70
- * basic client and server examples
71
-
72
- Documentation
73
- =============
74
-
75
- Available at https://python-osc.readthedocs.io/.
76
-
77
- Installation
78
- ============
79
-
80
- python-osc is a pure python library that has no external dependencies,
81
- to install it just use pip (prefered):
82
-
83
- .. image:: https://img.shields.io/pypi/v/python-osc.svg
84
- :target: https://pypi.python.org/pypi/python-osc
85
-
86
- .. code-block:: bash
87
-
88
- $ pip install python-osc
89
-
90
- Examples
91
- ========
92
-
93
- Simple client
94
- -------------
95
-
96
- .. code-block:: python
97
-
98
- """Small example OSC client
99
-
100
- This program sends 10 random values between 0.0 and 1.0 to the /filter address,
101
- waiting for 1 seconds between each value.
102
- """
103
- import argparse
104
- import random
105
- import time
106
-
107
- from pythonosc import udp_client
108
-
109
-
110
- if __name__ == "__main__":
111
- parser = argparse.ArgumentParser()
112
- parser.add_argument("--ip", default="127.0.0.1",
113
- help="The ip of the OSC server")
114
- parser.add_argument("--port", type=int, default=5005,
115
- help="The port the OSC server is listening on")
116
- args = parser.parse_args()
117
-
118
- client = udp_client.SimpleUDPClient(args.ip, args.port)
119
-
120
- for x in range(10):
121
- client.send_message("/filter", random.random())
122
- time.sleep(1)
123
-
124
- Simple server
125
- -------------
126
-
127
- .. code-block:: python
128
-
129
- """Small example OSC server
130
-
131
- This program listens to several addresses, and prints some information about
132
- received packets.
133
- """
134
- import argparse
135
- import math
136
-
137
- from pythonosc.dispatcher import Dispatcher
138
- from pythonosc import osc_server
139
-
140
- def print_volume_handler(unused_addr, args, volume):
141
- print("[{0}] ~ {1}".format(args[0], volume))
142
-
143
- def print_compute_handler(unused_addr, args, volume):
144
- try:
145
- print("[{0}] ~ {1}".format(args[0], args[1](volume)))
146
- except ValueError: pass
147
-
148
- if __name__ == "__main__":
149
- parser = argparse.ArgumentParser()
150
- parser.add_argument("--ip",
151
- default="127.0.0.1", help="The ip to listen on")
152
- parser.add_argument("--port",
153
- type=int, default=5005, help="The port to listen on")
154
- args = parser.parse_args()
155
-
156
- dispatcher = Dispatcher()
157
- dispatcher.map("/filter", print)
158
- dispatcher.map("/volume", print_volume_handler, "Volume")
159
- dispatcher.map("/logvolume", print_compute_handler, "Log volume", math.log)
160
-
161
- server = osc_server.ThreadingOSCUDPServer(
162
- (args.ip, args.port), dispatcher)
163
- print("Serving on {}".format(server.server_address))
164
- server.serve_forever()
165
-
166
- Building bundles
167
- ----------------
168
-
169
- .. code-block:: python
170
-
171
- from pythonosc import osc_bundle_builder
172
- from pythonosc import osc_message_builder
173
-
174
- bundle = osc_bundle_builder.OscBundleBuilder(
175
- osc_bundle_builder.IMMEDIATELY)
176
- msg = osc_message_builder.OscMessageBuilder(address="/SYNC")
177
- msg.add_arg(4.0)
178
- # Add 4 messages in the bundle, each with more arguments.
179
- bundle.add_content(msg.build())
180
- msg.add_arg(2)
181
- bundle.add_content(msg.build())
182
- msg.add_arg("value")
183
- bundle.add_content(msg.build())
184
- msg.add_arg(b"\x01\x02\x03")
185
- bundle.add_content(msg.build())
186
-
187
- sub_bundle = bundle.build()
188
- # Now add the same bundle inside itself.
189
- bundle.add_content(sub_bundle)
190
- # The bundle has 5 elements in total now.
191
-
192
- bundle = bundle.build()
193
- # You can now send it via a client with the `.send()` method:
194
- client.send(bundle)
195
-
196
- License?
197
- ========
198
- Unlicensed, do what you want with it. (http://unlicense.org)
@@ -1,37 +0,0 @@
1
- LICENSE.txt
2
- MANIFEST.in
3
- README.rst
4
- pyproject.toml
5
- python_osc.egg-info/PKG-INFO
6
- python_osc.egg-info/SOURCES.txt
7
- python_osc.egg-info/dependency_links.txt
8
- python_osc.egg-info/top_level.txt
9
- pythonosc/__init__.py
10
- pythonosc/dispatcher.py
11
- pythonosc/osc_bundle.py
12
- pythonosc/osc_bundle_builder.py
13
- pythonosc/osc_message.py
14
- pythonosc/osc_message_builder.py
15
- pythonosc/osc_packet.py
16
- pythonosc/osc_server.py
17
- pythonosc/osc_tcp_server.py
18
- pythonosc/slip.py
19
- pythonosc/tcp_client.py
20
- pythonosc/udp_client.py
21
- pythonosc/parsing/__init__.py
22
- pythonosc/parsing/ntp.py
23
- pythonosc/parsing/osc_types.py
24
- pythonosc/test/__init__.py
25
- pythonosc/test/test_dispatcher.py
26
- pythonosc/test/test_osc_bundle.py
27
- pythonosc/test/test_osc_bundle_builder.py
28
- pythonosc/test/test_osc_message.py
29
- pythonosc/test/test_osc_message_builder.py
30
- pythonosc/test/test_osc_packet.py
31
- pythonosc/test/test_osc_server.py
32
- pythonosc/test/test_osc_tcp_server.py
33
- pythonosc/test/test_tcp_client.py
34
- pythonosc/test/test_udp_client.py
35
- pythonosc/test/parsing/__init__.py
36
- pythonosc/test/parsing/test_ntp.py
37
- pythonosc/test/parsing/test_osc_types.py
@@ -1 +0,0 @@
1
- pythonosc
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
File without changes
File without changes