libusb1 3.2.0__tar.gz → 3.3.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 (34) hide show
  1. {libusb1-3.2.0/libusb1.egg-info → libusb1-3.3.0}/PKG-INFO +13 -1
  2. {libusb1-3.2.0 → libusb1-3.3.0}/README.rst +12 -0
  3. {libusb1-3.2.0 → libusb1-3.3.0/libusb1.egg-info}/PKG-INFO +13 -1
  4. {libusb1-3.2.0 → libusb1-3.3.0}/setup.py +28 -0
  5. {libusb1-3.2.0 → libusb1-3.3.0}/setup.sh +1 -1
  6. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/__init__.py +125 -100
  7. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/__pyinstaller/__init__.py +3 -0
  8. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/__pyinstaller/hook-usb1.py +4 -0
  9. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/__pyinstaller/test_libusb1_packaging.py +4 -1
  10. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/_version.py +3 -3
  11. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/testUSB1.py +42 -23
  12. {libusb1-3.2.0 → libusb1-3.3.0}/.gitattributes +0 -0
  13. {libusb1-3.2.0 → libusb1-3.3.0}/.gitignore +0 -0
  14. {libusb1-3.2.0 → libusb1-3.3.0}/.pylintrc +0 -0
  15. {libusb1-3.2.0 → libusb1-3.3.0}/COPYING +0 -0
  16. {libusb1-3.2.0 → libusb1-3.3.0}/COPYING.LESSER +0 -0
  17. {libusb1-3.2.0 → libusb1-3.3.0}/KEYS +0 -0
  18. {libusb1-3.2.0 → libusb1-3.3.0}/MANIFEST.in +0 -0
  19. {libusb1-3.2.0 → libusb1-3.3.0}/examples/README +0 -0
  20. {libusb1-3.2.0 → libusb1-3.3.0}/examples/hotplug.py +0 -0
  21. {libusb1-3.2.0 → libusb1-3.3.0}/examples/hotplug_advanced.py +0 -0
  22. {libusb1-3.2.0 → libusb1-3.3.0}/examples/listdevs.py +0 -0
  23. {libusb1-3.2.0 → libusb1-3.3.0}/examples/scan_device_tree.py +0 -0
  24. {libusb1-3.2.0 → libusb1-3.3.0}/libusb1.egg-info/SOURCES.txt +0 -0
  25. {libusb1-3.2.0 → libusb1-3.3.0}/libusb1.egg-info/dependency_links.txt +0 -0
  26. {libusb1-3.2.0 → libusb1-3.3.0}/libusb1.egg-info/entry_points.txt +0 -0
  27. {libusb1-3.2.0 → libusb1-3.3.0}/libusb1.egg-info/top_level.txt +0 -0
  28. {libusb1-3.2.0 → libusb1-3.3.0}/libusb1.py +0 -0
  29. {libusb1-3.2.0 → libusb1-3.3.0}/runTestLibusb.sh +0 -0
  30. {libusb1-3.2.0 → libusb1-3.3.0}/setup.cfg +0 -0
  31. {libusb1-3.2.0 → libusb1-3.3.0}/stdeb.cfg +0 -0
  32. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/_libusb1.py +0 -0
  33. {libusb1-3.2.0 → libusb1-3.3.0}/usb1/libusb1.py +0 -0
  34. {libusb1-3.2.0 → libusb1-3.3.0}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libusb1
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Pure-python wrapper for libusb-1.0
5
5
  Home-page: https://github.com/vpelletier/python-libusb1
6
6
  Author: Vincent Pelletier
@@ -596,9 +596,21 @@ Expose more modern libusb1 API:
596
596
  - libusb_set_log_cb
597
597
  - libusb_setlocale + libusb_strerror
598
598
  - libusb_wrap_sys_device
599
+ - transfer flags LIBUSB_TRANSFER_SHORT_NOT_OK and LIBUSB_TRANSFER_ADD_ZERO_PACKET
599
600
 
600
601
  As a result, python-libusb1 should now be usable on Android.
601
602
 
603
+ Fix tests on python 3.13 .
604
+
605
+ 3.3.0
606
+ -----
607
+
608
+ Improve deprecation warning, by showing the correct caller location.
609
+
610
+ Fix finalizer registration errors on pypi by using a thread-safe sequential number generator instead of relying on object id unicity and timely finalizer triggering.
611
+
612
+ Check in-dll version when creating Windows wheels. Should hopefully version mixups like what happened in 3.1.0 .
613
+
602
614
  .. _CPython: http://www.python.org/
603
615
 
604
616
  .. _pypy: http://pypy.org/
@@ -574,9 +574,21 @@ Expose more modern libusb1 API:
574
574
  - libusb_set_log_cb
575
575
  - libusb_setlocale + libusb_strerror
576
576
  - libusb_wrap_sys_device
577
+ - transfer flags LIBUSB_TRANSFER_SHORT_NOT_OK and LIBUSB_TRANSFER_ADD_ZERO_PACKET
577
578
 
578
579
  As a result, python-libusb1 should now be usable on Android.
579
580
 
581
+ Fix tests on python 3.13 .
582
+
583
+ 3.3.0
584
+ -----
585
+
586
+ Improve deprecation warning, by showing the correct caller location.
587
+
588
+ Fix finalizer registration errors on pypi by using a thread-safe sequential number generator instead of relying on object id unicity and timely finalizer triggering.
589
+
590
+ Check in-dll version when creating Windows wheels. Should hopefully version mixups like what happened in 3.1.0 .
591
+
580
592
  .. _CPython: http://www.python.org/
581
593
 
582
594
  .. _pypy: http://pypy.org/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libusb1
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Pure-python wrapper for libusb-1.0
5
5
  Home-page: https://github.com/vpelletier/python-libusb1
6
6
  Author: Vincent Pelletier
@@ -596,9 +596,21 @@ Expose more modern libusb1 API:
596
596
  - libusb_set_log_cb
597
597
  - libusb_setlocale + libusb_strerror
598
598
  - libusb_wrap_sys_device
599
+ - transfer flags LIBUSB_TRANSFER_SHORT_NOT_OK and LIBUSB_TRANSFER_ADD_ZERO_PACKET
599
600
 
600
601
  As a result, python-libusb1 should now be usable on Android.
601
602
 
603
+ Fix tests on python 3.13 .
604
+
605
+ 3.3.0
606
+ -----
607
+
608
+ Improve deprecation warning, by showing the correct caller location.
609
+
610
+ Fix finalizer registration errors on pypi by using a thread-safe sequential number generator instead of relying on object id unicity and timely finalizer triggering.
611
+
612
+ Check in-dll version when creating Windows wheels. Should hopefully version mixups like what happened in 3.1.0 .
613
+
602
614
  .. _CPython: http://www.python.org/
603
615
 
604
616
  .. _pypy: http://pypy.org/
@@ -17,6 +17,7 @@
17
17
  from setuptools import setup
18
18
  from setuptools import Command
19
19
  from codecs import open
20
+ import csv
20
21
  import hashlib
21
22
  from html.parser import HTMLParser
22
23
  import os
@@ -35,6 +36,7 @@ if os.getenv('I_KNOW_HOW_TO_RELEASE_PYTHON_LIBUSB1') != '1' and any(
35
36
  CURRENT_WINDOWS_7Z_SHA256 = (
36
37
  '19835e290f46fab6bd8ce4be6ab7dc5209f1c04bad177065df485e51dc4118c8'
37
38
  )
39
+ CURRENT_DLL_VERSION = '1.0.27.11882'
38
40
 
39
41
  cmdclass = versioneer.get_cmdclass()
40
42
  class upload(Command):
@@ -141,6 +143,32 @@ class update_libusb(Command):
141
143
  stdout=subprocess.DEVNULL,
142
144
  close_fds=True,
143
145
  )
146
+ out_dll = os.path.join(out_dir, 'libusb-1.0.dll')
147
+ assert os.path.exists(out_dll)
148
+ peres_stdout = subprocess.run(
149
+ [
150
+ 'peres',
151
+ '--format', 'csv',
152
+ '-v',
153
+ out_dll,
154
+ ],
155
+ check=True,
156
+ stdout=subprocess.PIPE,
157
+ encoding='ascii',
158
+ close_fds=True,
159
+ ).stdout
160
+ try:
161
+ for name, value in csv.reader(peres_stdout.splitlines()):
162
+ if name == 'Product Version':
163
+ if value != CURRENT_DLL_VERSION:
164
+ raise ValueError(
165
+ f'{out_dll} unexpected DLL version: {value}',
166
+ )
167
+ break
168
+ else:
169
+ raise ValueError('No "Product Version" in peres output')
170
+ except Exception as exc:
171
+ raise ValueError(f'Peres stdout: {peres_stdout!r}') from exc
144
172
  cmdclass['update_libusb'] = update_libusb
145
173
 
146
174
  setup(
@@ -29,5 +29,5 @@ twine check --strict "${release_prefix}"*.{whl,tar.gz}
29
29
  echo "Done. Next, check their content, sign each:"
30
30
  echo " for release in ${release_prefix}-*.whl ${release_prefix}.tar.gz; do gpg --armor --detach-sign \"\$release\"; done"
31
31
  echo "upload them to pypi:"
32
- echo " twine upload ${release_prefix}-*.whl{,.asc} ${release_prefix}.tar.gz{,.asc}"
32
+ echo " twine upload ${release_prefix}-*.whl ${release_prefix}.tar.gz"
33
33
  echo "and create a new release on github"
@@ -53,6 +53,7 @@ from ctypes import byref, c_int, sizeof, POINTER, \
53
53
  from ctypes.util import find_library
54
54
  import functools
55
55
  import inspect
56
+ import itertools
56
57
  import sys
57
58
  import threading
58
59
  import warnings
@@ -172,6 +173,50 @@ def create_binary_buffer(init_or_size):
172
173
  init_or_size = bytearray(init_or_size)
173
174
  return create_initialised_buffer(init_or_size)
174
175
 
176
+ class _LibUSB1Finalizer: # pylint: disable=too-few-public-methods
177
+ """
178
+ Create, and keep track of, finalizer objects.
179
+ Allows outstanding finalizers to be triggered, typically so C resources
180
+ owned by the objects associated with those finalizers are freed before
181
+ another one they depend (and owned by the instance of this class) on may
182
+ be freed.
183
+ """
184
+ def __init__(self):
185
+ self._finalizer_dict = {}
186
+ self.__finalizer_id_generator = itertools.count()
187
+ self.__finalizer_id_generator_lock = threading.Lock()
188
+
189
+ @staticmethod
190
+ def __finalize(handle, pop, func, kw):
191
+ try:
192
+ func(**kw)
193
+ finally:
194
+ pop(handle)
195
+
196
+ def _getFinalizer(self, obj, func, **kw):
197
+ """
198
+ Creates and adds to _finalizer_dict a finalizer which will trigger
199
+ after obj becomes unreachable and before it is garbage-collected.
200
+ When it triggers, func(**kw) is called and the finalizer is removed
201
+ from _finalizer_dict.
202
+
203
+ Returns the created finalizer object.
204
+ """
205
+ with self.__finalizer_id_generator_lock:
206
+ handle = next(self.__finalizer_id_generator)
207
+ finalizer_dict = self._finalizer_dict
208
+ finalizer_dict[handle] = finalizer = weakref.finalize(
209
+ obj,
210
+ functools.partial(
211
+ self.__finalize, # Note: static method
212
+ handle=handle,
213
+ pop=finalizer_dict.pop,
214
+ func=func,
215
+ kw=kw,
216
+ ),
217
+ )
218
+ return finalizer
219
+
175
220
  def create_initialised_buffer(init):
176
221
  # raises if init is an integer - this is intentional
177
222
  string_type = c_char * len(init)
@@ -219,8 +264,9 @@ class USBTransfer:
219
264
  iso_packets,
220
265
  before_submit,
221
266
  after_completion,
222
- registerFinalizer,
223
- unregisterFinalizer,
267
+ getFinalizer,
268
+ short_is_error,
269
+ add_zero_packet,
224
270
  ):
225
271
  """
226
272
  You should not instanciate this class directly.
@@ -241,20 +287,16 @@ class USBTransfer:
241
287
  raise USBErrorNoMem
242
288
  # pylint: enable=undefined-variable
243
289
  self.__transfer = transfer
244
- finalizer_handle = id(self)
245
- self.__close = weakref.finalize(
290
+ self.setShortIsError(short_is_error)
291
+ self.setAddZeroPacket(add_zero_packet)
292
+ self.__close = getFinalizer(
246
293
  self,
247
294
  self.__close, # Note: class method
248
295
  transfer=transfer,
249
296
  context=context,
250
- unregisterFinalizer=functools.partial(
251
- unregisterFinalizer,
252
- handle=finalizer_handle,
253
- ),
254
297
  libusb_free_transfer=libusb1.libusb_free_transfer,
255
298
  libusb_cancel_transfer=libusb1.libusb_cancel_transfer,
256
299
  )
257
- registerFinalizer(finalizer_handle, self.__close)
258
300
 
259
301
  def close(self):
260
302
  """
@@ -287,7 +329,6 @@ class USBTransfer:
287
329
  cls,
288
330
  transfer,
289
331
  context,
290
- unregisterFinalizer,
291
332
  libusb_free_transfer,
292
333
  libusb_cancel_transfer,
293
334
 
@@ -309,7 +350,6 @@ class USBTransfer:
309
350
  except USBErrorInterrupted_:
310
351
  pass
311
352
  libusb_free_transfer(transfer)
312
- unregisterFinalizer()
313
353
 
314
354
  def doom(self):
315
355
  """
@@ -720,6 +760,44 @@ class USBTransfer:
720
760
  transfer.buffer = cast(buff, c_void_p)
721
761
  transfer.length = sizeof(buff)
722
762
 
763
+ def isShortAnError(self):
764
+ """
765
+ Returns whether the LIBUSB_TRANSFER_SHORT_NOT_OK flag is set on this
766
+ transfer.
767
+ """
768
+ return bool(self.__transfer.contents.flags & libusb1.LIBUSB_TRANSFER_SHORT_NOT_OK)
769
+
770
+ def setShortIsError(self, state):
771
+ """
772
+ state (bool)
773
+ When true, LIBUSB_TRANSFER_SHORT_NOT_OK flag is set on this
774
+ transfer.
775
+ Otherwise, it is cleared.
776
+ """
777
+ if state:
778
+ self.__transfer.contents.flags |= libusb1.LIBUSB_TRANSFER_SHORT_NOT_OK
779
+ else:
780
+ self.__transfer.contents.flags &= ~libusb1.LIBUSB_TRANSFER_SHORT_NOT_OK
781
+
782
+ def isZeroPacketAdded(self):
783
+ """
784
+ Returns whether the LIBUSB_TRANSFER_ADD_ZERO_PACKET flag is set on this
785
+ transfer.
786
+ """
787
+ return bool(self.__transfer.contents.flags & libusb1.LIBUSB_TRANSFER_ADD_ZERO_PACKET)
788
+
789
+ def setAddZeroPacket(self, state):
790
+ """
791
+ state (bool)
792
+ When true, LIBUSB_TRANSFER_ADD_ZERO_PACKET flag is set on this
793
+ transfer.
794
+ Otherwise, it is cleared.
795
+ """
796
+ if state:
797
+ self.__transfer.contents.flags |= libusb1.LIBUSB_TRANSFER_ADD_ZERO_PACKET
798
+ else:
799
+ self.__transfer.contents.flags &= ~libusb1.LIBUSB_TRANSFER_ADD_ZERO_PACKET
800
+
723
801
  def isSubmitted(self):
724
802
  """
725
803
  Tells if this transfer is submitted and still pending.
@@ -986,7 +1064,7 @@ class _ReleaseInterface:
986
1064
  def __exit__(self, exc_type, exc_val, exc_tb):
987
1065
  self._handle.releaseInterface(self._interface)
988
1066
 
989
- class USBDeviceHandle:
1067
+ class USBDeviceHandle(_LibUSB1Finalizer):
990
1068
  """
991
1069
  Represents an opened USB device.
992
1070
  """
@@ -997,8 +1075,7 @@ class USBDeviceHandle:
997
1075
  context,
998
1076
  handle,
999
1077
  device,
1000
- registerFinalizer,
1001
- unregisterFinalizer,
1078
+ getFinalizer,
1002
1079
  can_close_device,
1003
1080
  ):
1004
1081
  """
@@ -1006,8 +1083,8 @@ class USBDeviceHandle:
1006
1083
  Call "open" method on an USBDevice instance to get an USBDeviceHandle
1007
1084
  instance.
1008
1085
  """
1086
+ super().__init__()
1009
1087
  self.__context = context
1010
- self.__finalizer_dict = {}
1011
1088
  # Strong references to inflight transfers so they do not get freed
1012
1089
  # even if user drops all strong references to them. If this instance
1013
1090
  # is garbage-collected, we close all transfers, so it's fine.
@@ -1020,8 +1097,7 @@ class USBDeviceHandle:
1020
1097
  self.__inflight_remove = inflight.remove
1021
1098
  self.__handle = handle
1022
1099
  self.__device = device
1023
- finalizer_handle = id(self)
1024
- self.close = weakref.finalize(
1100
+ self.close = getFinalizer(
1025
1101
  self,
1026
1102
  self.close, # Note: static method
1027
1103
  context=context,
@@ -1032,23 +1108,9 @@ class USBDeviceHandle:
1032
1108
  None
1033
1109
  ),
1034
1110
  inflight=inflight,
1035
- finalizer_dict=self.__finalizer_dict,
1036
- unregisterFinalizer=functools.partial(
1037
- unregisterFinalizer,
1038
- handle=finalizer_handle,
1039
- ),
1111
+ finalizer_dict=self._finalizer_dict,
1040
1112
  libusb_close=libusb1.libusb_close,
1041
1113
  )
1042
- registerFinalizer(finalizer_handle, self.close)
1043
-
1044
- def __registerFinalizer(self, handle, finalizer):
1045
- if handle in self.__finalizer_dict:
1046
- finalizer.detach()
1047
- raise ValueError('Finalizer handle {handle} already exists')
1048
- self.__finalizer_dict[handle] = finalizer
1049
-
1050
- def __unregisterFinalizer(self, handle):
1051
- self.__finalizer_dict.pop(handle)
1052
1114
 
1053
1115
  @staticmethod
1054
1116
  def close( # pylint: disable=method-hidden
@@ -1057,7 +1119,6 @@ class USBDeviceHandle:
1057
1119
  device,
1058
1120
  inflight,
1059
1121
  finalizer_dict,
1060
- unregisterFinalizer,
1061
1122
  libusb_close,
1062
1123
 
1063
1124
  set_=set,
@@ -1098,7 +1159,6 @@ class USBDeviceHandle:
1098
1159
  if device is not None:
1099
1160
  device.close()
1100
1161
  libusb_close(handle)
1101
- unregisterFinalizer()
1102
1162
 
1103
1163
  def getDevice(self):
1104
1164
  """
@@ -1508,11 +1568,14 @@ class USBDeviceHandle:
1508
1568
  raise
1509
1569
  return data_buffer[:transferred]
1510
1570
 
1511
- def getTransfer(self, iso_packets=0):
1571
+ def getTransfer(self, iso_packets=0, short_is_error=False, add_zero_packet=False):
1512
1572
  """
1513
1573
  Get an USBTransfer instance for asynchronous use.
1514
1574
  iso_packets: the number of isochronous transfer descriptors to
1515
1575
  allocate.
1576
+ short_is_error: When true, short frames are reported as errors.
1577
+ add_zero_packet: When true, transfers of a multiple of the endpoint
1578
+ size are followed by a zero-length packet.
1516
1579
  """
1517
1580
  return USBTransfer(
1518
1581
  context=self.__context,
@@ -1520,8 +1583,9 @@ class USBDeviceHandle:
1520
1583
  iso_packets=iso_packets,
1521
1584
  before_submit=self.__inflight_add,
1522
1585
  after_completion=self.__inflight_remove,
1523
- registerFinalizer=self.__registerFinalizer,
1524
- unregisterFinalizer=self.__unregisterFinalizer,
1586
+ getFinalizer=self._getFinalizer,
1587
+ short_is_error=short_is_error,
1588
+ add_zero_packet=add_zero_packet,
1525
1589
  )
1526
1590
 
1527
1591
  class USBConfiguration:
@@ -1734,7 +1798,7 @@ class USBEndpoint:
1734
1798
  def getExtra(self):
1735
1799
  return libusb1.get_extra(self.__endpoint)
1736
1800
 
1737
- class USBDevice:
1801
+ class USBDevice(_LibUSB1Finalizer):
1738
1802
  """
1739
1803
  Represents a USB device.
1740
1804
 
@@ -1750,8 +1814,7 @@ class USBDevice:
1750
1814
  self,
1751
1815
  context,
1752
1816
  device_p,
1753
- registerFinalizer,
1754
- unregisterFinalizer,
1817
+ getFinalizer,
1755
1818
  can_load_configuration,
1756
1819
  can_change_refcount,
1757
1820
  handle_p,
@@ -1760,13 +1823,12 @@ class USBDevice:
1760
1823
  You should not instanciate this class directly.
1761
1824
  Call USBContext methods to receive instances of this class.
1762
1825
  """
1826
+ super().__init__()
1763
1827
  self.__context = context
1764
- self.__finalizer_dict = finalizer_dict = {}
1765
1828
  self.__configuration_descriptor_list = descriptor_list = []
1766
1829
  if can_change_refcount:
1767
1830
  libusb1.libusb_ref_device(device_p)
1768
- finalizer_handle = id(self)
1769
- self.close = weakref.finalize(
1831
+ self.close = getFinalizer(
1770
1832
  self,
1771
1833
  self.close, # Note: static method
1772
1834
  device_p=(
@@ -1774,16 +1836,11 @@ class USBDevice:
1774
1836
  if can_change_refcount else
1775
1837
  None
1776
1838
  ),
1777
- finalizer_dict=finalizer_dict,
1778
- unregisterFinalizer=functools.partial(
1779
- unregisterFinalizer,
1780
- handle=finalizer_handle,
1781
- ),
1839
+ finalizer_dict=self._finalizer_dict,
1782
1840
  descriptor_list=descriptor_list,
1783
1841
  libusb_unref_device=libusb1.libusb_unref_device,
1784
1842
  libusb_free_config_descriptor=libusb1.libusb_free_config_descriptor,
1785
1843
  )
1786
- registerFinalizer(finalizer_handle, self.close)
1787
1844
  self.device_p = device_p
1788
1845
  # Fetch device descriptor
1789
1846
  # Note: if this is made lazy, access errors will happen later, breaking
@@ -1817,25 +1874,14 @@ class USBDevice:
1817
1874
  context=context,
1818
1875
  handle=handle_p,
1819
1876
  device=self,
1820
- registerFinalizer=self.__registerFinalizer,
1821
- unregisterFinalizer=self.__unregisterFinalizer,
1877
+ getFinalizer=self._getFinalizer,
1822
1878
  can_close_device=True,
1823
1879
  )
1824
1880
 
1825
- def __registerFinalizer(self, handle, finalizer):
1826
- if handle in self.__finalizer_dict:
1827
- finalizer.detach()
1828
- raise ValueError('Finalizer handle {handle} already exists')
1829
- self.__finalizer_dict[handle] = finalizer
1830
-
1831
- def __unregisterFinalizer(self, handle):
1832
- self.__finalizer_dict.pop(handle)
1833
-
1834
1881
  @staticmethod
1835
1882
  def close( # pylint: disable=method-hidden
1836
1883
  device_p,
1837
1884
  finalizer_dict,
1838
- unregisterFinalizer,
1839
1885
  descriptor_list,
1840
1886
  libusb_unref_device,
1841
1887
  libusb_free_config_descriptor,
@@ -1852,7 +1898,6 @@ class USBDevice:
1852
1898
  libusb_free_config_descriptor(
1853
1899
  byref_(descriptor_list.pop()),
1854
1900
  )
1855
- unregisterFinalizer()
1856
1901
 
1857
1902
  def __str__(self):
1858
1903
  return (
@@ -2093,8 +2138,7 @@ class USBDevice:
2093
2138
  context=self.__context,
2094
2139
  handle=handle,
2095
2140
  device=self,
2096
- registerFinalizer=self.__registerFinalizer,
2097
- unregisterFinalizer=self.__unregisterFinalizer,
2141
+ getFinalizer=self._getFinalizer,
2098
2142
  can_close_device=False,
2099
2143
  )
2100
2144
 
@@ -2103,7 +2147,7 @@ _zero_tv_p = byref(_zero_tv)
2103
2147
  _null_pointer = c_void_p()
2104
2148
  _NULL_LOG_CALLBACK = libusb1.libusb_log_cb_p(0)
2105
2149
 
2106
- class USBContext:
2150
+ class USBContext(_LibUSB1Finalizer):
2107
2151
  """
2108
2152
  libusb1 USB context.
2109
2153
 
@@ -2131,6 +2175,7 @@ class USBContext:
2131
2175
  'Use "with USBContext() as context:" for safer cleanup'
2132
2176
  ' on interpreter shutdown. See also USBContext.open().',
2133
2177
  DeprecationWarning,
2178
+ stacklevel=4
2134
2179
  )
2135
2180
  self.open()
2136
2181
  self.__context_refcount += 1
@@ -2193,6 +2238,7 @@ class USBContext:
2193
2238
  __enter__, open, or the first context-dependent call, whichever happens
2194
2239
  first) to fail if libusb is older than v1.0.27 .
2195
2240
  """
2241
+ super().__init__()
2196
2242
  # Used to prevent an exit to cause a segfault if a concurrent thread
2197
2243
  # is still in libusb.
2198
2244
  self.__context_refcount = 0
@@ -2200,7 +2246,6 @@ class USBContext:
2200
2246
  self.__context_p = libusb1.libusb_context_p()
2201
2247
  assert not self.__context_p
2202
2248
  self.__hotplug_callback_dict = {}
2203
- self.__finalizer_dict = {}
2204
2249
  self.__log_level = log_level
2205
2250
  self.__use_usbdk = use_usbdk
2206
2251
  self.__with_device_discovery = with_device_discovery
@@ -2213,15 +2258,6 @@ class USBContext:
2213
2258
  def __exit__(self, exc_type, exc_val, exc_tb):
2214
2259
  self.close()
2215
2260
 
2216
- def __registerFinalizer(self, handle, finalizer):
2217
- if handle in self.__finalizer_dict:
2218
- finalizer.detach()
2219
- raise ValueError('Finalizer handle {handle} already exists')
2220
- self.__finalizer_dict[handle] = finalizer
2221
-
2222
- def __unregisterFinalizer(self, handle):
2223
- self.__finalizer_dict.pop(handle)
2224
-
2225
2261
  def open(self):
2226
2262
  """
2227
2263
  Finish context initialisation, as is normally done in __enter__ .
@@ -2274,7 +2310,7 @@ class USBContext:
2274
2310
  self.___close, # Note: static method
2275
2311
  context_p=self.__context_p,
2276
2312
  hotplug_callback_dict=self.__hotplug_callback_dict,
2277
- finalizer_dict=self.__finalizer_dict,
2313
+ finalizer_dict=self._finalizer_dict,
2278
2314
  libusb_exit=libusb1.libusb_exit,
2279
2315
  libusb_hotplug_deregister_callback=libusb1.libusb_hotplug_deregister_callback,
2280
2316
  )
@@ -2353,8 +2389,7 @@ class USBContext:
2353
2389
  device = USBDevice(
2354
2390
  context=self,
2355
2391
  device_p=device_p,
2356
- registerFinalizer=self.__registerFinalizer,
2357
- unregisterFinalizer=self.__unregisterFinalizer,
2392
+ getFinalizer=self._getFinalizer,
2358
2393
  can_load_configuration=True,
2359
2394
  can_change_refcount=True,
2360
2395
  handle_p=None,
@@ -2454,8 +2489,7 @@ class USBContext:
2454
2489
  return USBDevice(
2455
2490
  context=self,
2456
2491
  device_p=libusb1.libusb_get_device(handle_p),
2457
- registerFinalizer=self.__registerFinalizer,
2458
- unregisterFinalizer=self.__unregisterFinalizer,
2492
+ getFinalizer=self._getFinalizer,
2459
2493
  can_load_configuration=True, # XXX: give the caller control ?
2460
2494
  can_change_refcount=False,
2461
2495
  handle_p=handle_p,
@@ -2564,28 +2598,21 @@ class USBContext:
2564
2598
  user_data,
2565
2599
  )
2566
2600
  if not self.__has_pollfd_finalizer:
2601
+ # Note: the above condition is just to avoid creating finalizers on
2602
+ # every call. If more than one is created (because of a
2603
+ # race-condition) it is not a big deal, as __finalizePollFDNotifiers
2604
+ # will do the right thing even if called multiple times in a row.
2567
2605
  self.__has_pollfd_finalizer = True
2568
- try:
2569
- finalizer_handle = id(self)
2570
- finalizer_dict = self.__finalizer_dict
2571
- finalizer = weakref.finalize(
2572
- self,
2573
- self.__finalizePollFDNotifiers, # Note: staticmethod
2574
- context_p=self.__context_p,
2575
- unregisterFinalizer=functools.partial(
2576
- finalizer_dict.pop,
2577
- finalizer_handle,
2578
- ),
2579
- libusb_set_pollfd_notifiers=libusb1.libusb_set_pollfd_notifiers,
2580
- )
2581
- self.__registerFinalizer(finalizer_handle, finalizer)
2582
- except ValueError: # Already registered
2583
- pass
2606
+ self.getFinalizer(
2607
+ self,
2608
+ self.__finalizePollFDNotifiers, # Note: staticmethod
2609
+ context_p=self.__context_p,
2610
+ libusb_set_pollfd_notifiers=libusb1.libusb_set_pollfd_notifiers,
2611
+ )
2584
2612
 
2585
2613
  @staticmethod
2586
2614
  def __finalizePollFDNotifiers(
2587
2615
  context_p,
2588
- unregisterFinalizer,
2589
2616
  libusb_set_pollfd_notifiers,
2590
2617
 
2591
2618
  null_pointer=_null_pointer,
@@ -2598,7 +2625,6 @@ class USBContext:
2598
2625
  removed_cb_p,
2599
2626
  null_pointer,
2600
2627
  )
2601
- unregisterFinalizer()
2602
2628
 
2603
2629
  @_validContext
2604
2630
  def getNextTimeout(self):
@@ -2756,8 +2782,7 @@ class USBContext:
2756
2782
  device = USBDevice(
2757
2783
  context=self,
2758
2784
  device_p=device_p,
2759
- registerFinalizer=self.__registerFinalizer,
2760
- unregisterFinalizer=self.__unregisterFinalizer,
2785
+ getFinalizer=self._getFinalizer,
2761
2786
  # pylint: disable=undefined-variable
2762
2787
  can_load_configuration=event != HOTPLUG_EVENT_DEVICE_LEFT,
2763
2788
  # pylint: enable=undefined-variable
@@ -1,3 +1,6 @@
1
+ # pylint: disable=missing-module-docstring
2
+ # pylint: disable=missing-function-docstring
3
+ # pylint: disable=invalid-name
1
4
  import os
2
5
 
3
6
  def get_hook_dirs():
@@ -1,5 +1,9 @@
1
+ # pylint: disable=missing-module-docstring
2
+ # pylint: disable=invalid-name
1
3
  import logging
4
+ # pylint: disable=import-error
2
5
  from PyInstaller.utils.hooks import collect_dynamic_libs
6
+ # pylint: enable=import-error
3
7
 
4
8
  logger = logging.getLogger(__name__)
5
9
  logger.info("--- libusb1 pyinstaller hook ---")
@@ -10,8 +10,11 @@ Instructions to run these tests:
10
10
  > python -m PyInstaller.utils.run_tests --include_only usb1
11
11
 
12
12
  """
13
+ # pylint: disable=missing-function-docstring
13
14
  import subprocess
15
+ # pylint: disable=import-error
14
16
  from PyInstaller import __main__ as pyi_main
17
+ # pylint: enable=import-error
15
18
 
16
19
 
17
20
  def test_pyi_hooksample(tmp_path):
@@ -31,4 +34,4 @@ def test_pyi_hooksample(tmp_path):
31
34
  str(app),
32
35
  ]
33
36
  pyi_main.run(args)
34
- subprocess.run([str(distpath / app_name / app_name)], check=True)
37
+ subprocess.run([str(distpath / app_name / app_name)], check=True)
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-12-28T12:45:41+0000",
11
+ "date": "2025-03-04T04:49:54+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "5a21cb367cc07258a4a4feda996dbfb20da7012c",
15
- "version": "3.2.0"
14
+ "full-revisionid": "2dd834dad913a89c68309ac1afa0efc0e55b606f",
15
+ "version": "3.3.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -21,6 +21,7 @@ import functools
21
21
  import gc
22
22
  import itertools
23
23
  import unittest
24
+ import warnings
24
25
  import weakref
25
26
  import usb1
26
27
  from . import libusb1
@@ -35,10 +36,10 @@ class USBContext(usb1.USBContext):
35
36
  def open(self):
36
37
  try:
37
38
  return super().open()
38
- except usb1.USBError:
39
+ except usb1.USBError as exc:
39
40
  raise unittest.SkipTest(
40
41
  'usb1.USBContext() fails - no USB bus on system ?'
41
- )
42
+ ) from exc
42
43
 
43
44
  def checkTransferAllocCount(func):
44
45
  @functools.wraps(func)
@@ -85,7 +86,7 @@ class USBTransferTests(unittest.TestCase):
85
86
  return pointer(transfer)
86
87
 
87
88
  @staticmethod
88
- def getTransfer(iso_packets=0):
89
+ def getTransfer(iso_packets=0, short_is_error=False, add_zero_packet=False):
89
90
  # Dummy handle
90
91
  return usb1.USBTransfer(
91
92
  context=None,
@@ -93,8 +94,11 @@ class USBTransferTests(unittest.TestCase):
93
94
  iso_packets=iso_packets,
94
95
  before_submit=lambda x: None,
95
96
  after_completion=lambda x: None,
96
- registerFinalizer=lambda handle, finalizer: None,
97
- unregisterFinalizer=lambda handle: None,
97
+ getFinalizer=(
98
+ lambda obj, func, **kw: weakref.finalize(obj, func, **kw)
99
+ ),
100
+ short_is_error=short_is_error,
101
+ add_zero_packet=add_zero_packet,
98
102
  )
99
103
 
100
104
  @staticmethod
@@ -109,7 +113,7 @@ class USBTransferTests(unittest.TestCase):
109
113
  """
110
114
  Just testing hasCapability doesn't raise...
111
115
  """
112
- usb1.hasCapability(usb1.CAP_HAS_CAPABILITY)
116
+ usb1.hasCapability(usb1.CAP_HAS_CAPABILITY) # pylint: disable=no-member
113
117
 
114
118
  @checkTransferAllocCount
115
119
  def testSetControl(self):
@@ -117,11 +121,11 @@ class USBTransferTests(unittest.TestCase):
117
121
  Simplest test: feed some data, must not raise.
118
122
  """
119
123
  transfer = self.getTransfer()
120
- request_type = usb1.TYPE_STANDARD
121
- request = usb1.REQUEST_GET_STATUS
124
+ request_type = usb1.TYPE_STANDARD # pylint: disable=no-member
125
+ request = usb1.REQUEST_GET_STATUS # pylint: disable=no-member
122
126
  value = 0
123
127
  index = 0
124
- def callback(transfer):
128
+ def callback(_):
125
129
  pass
126
130
  user_data = []
127
131
  timeout = 1000
@@ -145,10 +149,24 @@ class USBTransferTests(unittest.TestCase):
145
149
  request_type, request, value, index, buff, callback=callback)
146
150
  # No callback
147
151
  transfer.setControl(request_type, request, value, index, buff)
152
+ self.assertFalse(transfer.isShortAnError())
153
+ self.assertFalse(transfer.isZeroPacketAdded())
154
+ transfer.setShortIsError(True)
155
+ self.assertTrue(transfer.isShortAnError())
156
+ self.assertFalse(transfer.isZeroPacketAdded())
157
+ transfer.setAddZeroPacket(True)
158
+ self.assertTrue(transfer.isShortAnError())
159
+ self.assertTrue(transfer.isZeroPacketAdded())
160
+ transfer.setShortIsError(False)
161
+ self.assertFalse(transfer.isShortAnError())
162
+ self.assertTrue(transfer.isZeroPacketAdded())
163
+ transfer.setAddZeroPacket(False)
164
+ self.assertFalse(transfer.isShortAnError())
165
+ self.assertFalse(transfer.isZeroPacketAdded())
148
166
 
149
167
  def _testTransferSetter(self, transfer, setter_id):
150
168
  endpoint = 0x81
151
- def callback(transfer):
169
+ def callback(_):
152
170
  pass
153
171
  user_data = []
154
172
  timeout = 1000
@@ -226,7 +244,7 @@ class USBTransferTests(unittest.TestCase):
226
244
  @checkTransferAllocCount
227
245
  def testSetGetCallback(self):
228
246
  transfer = self.getTransfer()
229
- def callback(transfer):
247
+ def callback(_):
230
248
  pass
231
249
  transfer.setCallback(callback)
232
250
  got_callback = transfer.getCallback()
@@ -237,7 +255,7 @@ class USBTransferTests(unittest.TestCase):
237
255
  Test descriptor walk.
238
256
  Needs any usb device, which won't be opened.
239
257
  """
240
- with USBContext() as context:
258
+ with USBContext() as context: # pylint: disable=too-many-nested-blocks
241
259
  device_list = context.getDeviceList(skip_on_error=True)
242
260
  found = False
243
261
  seen_extra = False
@@ -281,9 +299,7 @@ class USBTransferTests(unittest.TestCase):
281
299
  self.assertEqual(locals().get(ENUM_NAME), None)
282
300
  self.assertEqual(global_dict.get(ENUM_NAME), None)
283
301
  self.assertEqual(getattr(libusb1, ENUM_NAME, None), None)
284
- # pylint: disable=unused-variable
285
- TEST_ENUM = libusb1.Enum({ENUM_NAME: ENUM_VALUE})
286
- # pylint: enable=unused-variable
302
+ _ = libusb1.Enum({ENUM_NAME: ENUM_VALUE})
287
303
  self.assertEqual(locals().get(ENUM_NAME), ENUM_VALUE)
288
304
  self.assertEqual(global_dict.get(ENUM_NAME), None)
289
305
  self.assertEqual(getattr(libusb1, ENUM_NAME, None), None)
@@ -298,9 +314,7 @@ class USBTransferTests(unittest.TestCase):
298
314
  self.assertEqual(locals().get(ENUM_NAME), None)
299
315
  self.assertEqual(global_dict.get(ENUM_NAME), None)
300
316
  self.assertEqual(getattr(libusb1, ENUM_NAME, None), None)
301
- # pylint: disable=unused-variable
302
- TEST_ENUM = libusb1.Enum({ENUM_NAME: ENUM_VALUE}, global_dict)
303
- # pylint: enable=unused-variable
317
+ _ = libusb1.Enum({ENUM_NAME: ENUM_VALUE}, global_dict)
304
318
  try:
305
319
  self.assertEqual(locals().get(ENUM_NAME), None)
306
320
  self.assertEqual(global_dict.get(ENUM_NAME), ENUM_VALUE)
@@ -316,9 +330,14 @@ class USBTransferTests(unittest.TestCase):
316
330
  """
317
331
  context = USBContext() # Deprecated
318
332
  try:
333
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
319
334
  fd_list = context.getPollFDList()
320
335
  except NotImplementedError:
321
- raise unittest.SkipTest('libusb without file descriptor events')
336
+ raise unittest.SkipTest(
337
+ 'libusb without file descriptor events',
338
+ ) from None
339
+ finally:
340
+ warnings.resetwarnings()
322
341
  self.assertNotEqual(fd_list, None)
323
342
  context.exit() # Deprecated
324
343
  self.assertEqual(context.getPollFDList(), None)
@@ -336,7 +355,7 @@ class USBTransferTests(unittest.TestCase):
336
355
  try:
337
356
  usb1.setLogCallback(callback)
338
357
  with USBContext(
339
- log_level=usb1.LOG_LEVEL_DEBUG,
358
+ log_level=usb1.LOG_LEVEL_DEBUG, # pylint: disable=no-member
340
359
  ):
341
360
  pass
342
361
  finally:
@@ -349,7 +368,7 @@ class USBTransferTests(unittest.TestCase):
349
368
  message_list = []
350
369
  def callback(context, level, message):
351
370
  message_list.append((context, level, message))
352
- def log_silencer(context, level, message):
371
+ def log_silencer(_, __, ___):
353
372
  pass
354
373
  try:
355
374
  # Note: silencing global logs is needed here because we lower the
@@ -358,7 +377,7 @@ class USBTransferTests(unittest.TestCase):
358
377
  # to stderr.
359
378
  usb1.setLogCallback(log_silencer)
360
379
  with USBContext(
361
- log_level=usb1.LOG_LEVEL_DEBUG,
380
+ log_level=usb1.LOG_LEVEL_DEBUG, # pylint: disable=no-member
362
381
  log_callback=callback,
363
382
  ) as ctx:
364
383
  ctx.setLogCallback(None)
@@ -369,7 +388,7 @@ class USBTransferTests(unittest.TestCase):
369
388
  def testSetLocale(self):
370
389
  if not hasattr(libusb1, 'libusb_setlocale'):
371
390
  raise unittest.SkipTest('libusb without libusb_setlocale')
372
- err = usb1.USBErrorIO()
391
+ err = usb1.USBErrorIO() # pylint: disable=no-member
373
392
  usb1.setLocale('en')
374
393
  caption_en = err.getMessage()
375
394
  self.assertTrue(caption_en)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes