swbt-python 0.5.2__py3-none-any.whl → 0.5.3__py3-none-any.whl

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.
@@ -1,33 +1,17 @@
1
- """ACL queue drain helper for Bumble HID transport."""
1
+ """ACL queue drain helper for the pinned Bumble HID transport."""
2
2
 
3
- from collections.abc import Awaitable
3
+ from typing import Any, cast
4
4
 
5
5
 
6
6
  async def drain_bumble_acl_queue(l2cap_channel: object) -> None:
7
- """Wait until Bumble reports no pending ACL packets for the channel."""
8
- connection = getattr(l2cap_channel, "connection", None)
9
- connection_handle = getattr(connection, "handle", None)
10
- acl_packet_queue = getattr(connection, "acl_packet_queue", None)
11
- if acl_packet_queue is None and isinstance(connection_handle, int):
12
- device = getattr(connection, "device", None)
13
- host = getattr(device, "host", None)
14
- get_data_packet_queue = getattr(host, "get_data_packet_queue", None)
15
- if callable(get_data_packet_queue):
16
- acl_packet_queue = get_data_packet_queue(connection_handle)
17
- drain = getattr(acl_packet_queue, "drain", None)
18
- if not isinstance(connection_handle, int) or not callable(drain):
7
+ """Wait for the pinned Bumble connection's pending ACL packets to complete."""
8
+ channel = cast("Any", l2cap_channel)
9
+ connection = channel.connection
10
+ acl_packet_queue = connection.device.host.get_data_packet_queue(connection.handle)
11
+ if acl_packet_queue is None:
19
12
  return
20
13
  try:
21
- last_pending: int | None = None
22
- while True:
23
- pending = getattr(acl_packet_queue, "pending", 0)
24
- if not isinstance(pending, int) or pending <= 0 or pending == last_pending:
25
- return
26
- last_pending = pending
27
- drain_result = drain(connection_handle)
28
- if isinstance(drain_result, Awaitable):
29
- await drain_result
30
- continue
31
- return
14
+ await acl_packet_queue.drain(connection.handle)
32
15
  except ValueError:
16
+ # Bumble raises when the queue has no state for a channel that was already drained.
33
17
  return
@@ -9,11 +9,9 @@ from swbt.diagnostics import DiagnosticsRecorder
9
9
  from swbt.errors import InvalidKeyStoreError
10
10
  from swbt.transport._pairing_profile import KeyStoreNamespaces, PairingProfile
11
11
 
12
- PREVIOUS_NAMESPACE_PREFIX = "swbt.previous::"
13
-
14
12
 
15
13
  class _PairingProfileKeyStore:
16
- """Bumble-compatible profile key store with one previous generation."""
14
+ """Bumble-compatible profile key store for the current namespace."""
17
15
 
18
16
  def __init__(
19
17
  self,
@@ -24,7 +22,6 @@ class _PairingProfileKeyStore:
24
22
  self._profile_path = Path(profile_path)
25
23
  self._namespace = namespace if isinstance(namespace, str) else None
26
24
  self._namespace_resolver = None if isinstance(namespace, str) else namespace
27
- self.last_update_previous_saved = False
28
25
 
29
26
  def _resolve_namespace(self) -> str:
30
27
  namespace = (
@@ -36,16 +33,9 @@ class _PairingProfileKeyStore:
36
33
  return namespace
37
34
 
38
35
  async def update(self, name: str, keys: object) -> None:
39
- """Write current keys and keep the overwritten current value as previous."""
36
+ """Replace the current namespace with one peer's keys."""
40
37
  current_store = self._current_store()
41
38
  db, current_key_map = await current_store.load()
42
- previous_namespace = self._previous_namespace(current_store)
43
- previous_key_map = copy.deepcopy(current_key_map)
44
- self.last_update_previous_saved = bool(previous_key_map)
45
- if previous_key_map:
46
- db[previous_namespace] = previous_key_map
47
- else:
48
- db.pop(previous_namespace, None)
49
39
  current_key_map.clear()
50
40
  current_key_map[name] = cast("Any", keys).to_dict()
51
41
  await current_store.save(db)
@@ -84,10 +74,6 @@ class _PairingProfileKeyStore:
84
74
  adapter_default=self._namespace_resolver is not None,
85
75
  )
86
76
 
87
- @staticmethod
88
- def _previous_namespace(current_store: "_PairingProfileNamespaceStore") -> str:
89
- return f"{PREVIOUS_NAMESPACE_PREFIX}{current_store.namespace}"
90
-
91
77
 
92
78
  class _PairingProfileNamespaceStore:
93
79
  """JsonKeyStore-compatible view over one profile namespace map."""
@@ -169,20 +155,16 @@ class _DiagnosticKeyStore:
169
155
  try:
170
156
  await cast("Any", self._key_store).update(name, keys)
171
157
  except Exception as error:
172
- fields = self._generation_fields()
173
158
  self._diagnostics.record_event(
174
159
  "key_store_update",
175
- **fields,
176
160
  error_type=type(error).__name__,
177
161
  message=str(error),
178
162
  peer_address=name,
179
163
  status="failed",
180
164
  )
181
165
  raise
182
- fields = self._generation_fields()
183
166
  self._diagnostics.record_event(
184
167
  "key_store_update",
185
- **fields,
186
168
  peer_address=name,
187
169
  status="succeeded",
188
170
  )
@@ -209,11 +191,3 @@ class _DiagnosticKeyStore:
209
191
 
210
192
  def __getattr__(self, name: str) -> object:
211
193
  return getattr(self._key_store, name)
212
-
213
- def _generation_fields(self) -> dict[str, object]:
214
- if not isinstance(self._key_store, _PairingProfileKeyStore):
215
- return {}
216
- return {
217
- "generation": "current",
218
- "previous_saved": self._key_store.last_update_previous_saved,
219
- }
@@ -25,20 +25,19 @@ class ConnectionDiagnostics:
25
25
  self._record_event = record_event
26
26
 
27
27
  def register(self, connection: object) -> None:
28
- """Register all connection diagnostics callbacks supported by the connection."""
29
- on_event = getattr(connection, "on", None)
30
- if not callable(on_event):
31
- return
28
+ """Register all diagnostics callbacks provided by Bumble 0.0.233."""
29
+ connection_with_attrs = cast("Any", connection)
30
+ on_event = connection_with_attrs.on
32
31
  on_event(
33
- getattr(connection, "EVENT_DISCONNECTION", "disconnection"),
32
+ connection_with_attrs.EVENT_DISCONNECTION,
34
33
  self._handle_disconnection,
35
34
  )
36
35
  on_event(
37
- getattr(connection, "EVENT_CLASSIC_PAIRING", "classic_pairing"),
36
+ connection_with_attrs.EVENT_CLASSIC_PAIRING,
38
37
  lambda *_args: self._record_event("classic_pairing", adapter=self._adapter),
39
38
  )
40
39
  on_event(
41
- getattr(connection, "EVENT_CLASSIC_PAIRING_FAILURE", "classic_pairing_failure"),
40
+ connection_with_attrs.EVENT_CLASSIC_PAIRING_FAILURE,
42
41
  lambda reason=None, *_args: self._record_event(
43
42
  "classic_pairing_failure",
44
43
  adapter=self._adapter,
@@ -46,15 +45,15 @@ class ConnectionDiagnostics:
46
45
  ),
47
46
  )
48
47
  on_event(
49
- getattr(connection, "EVENT_PAIRING_START", "pairing_start"),
48
+ connection_with_attrs.EVENT_PAIRING_START,
50
49
  lambda *_args: self._record_event("pairing_start", adapter=self._adapter),
51
50
  )
52
51
  on_event(
53
- getattr(connection, "EVENT_PAIRING", "pairing"),
52
+ connection_with_attrs.EVENT_PAIRING,
54
53
  lambda keys=None, *_args: self._record_pairing_complete(connection, keys),
55
54
  )
56
55
  on_event(
57
- getattr(connection, "EVENT_PAIRING_FAILURE", "pairing_failure"),
56
+ connection_with_attrs.EVENT_PAIRING_FAILURE,
58
57
  lambda reason=None, *_args: self._record_event(
59
58
  "pairing_failure",
60
59
  adapter=self._adapter,
@@ -62,15 +61,11 @@ class ConnectionDiagnostics:
62
61
  ),
63
62
  )
64
63
  on_event(
65
- getattr(connection, "EVENT_CONNECTION_AUTHENTICATION", "connection_authentication"),
64
+ connection_with_attrs.EVENT_CONNECTION_AUTHENTICATION,
66
65
  lambda *_args: self._record_connection_authentication(connection),
67
66
  )
68
67
  on_event(
69
- getattr(
70
- connection,
71
- "EVENT_CONNECTION_AUTHENTICATION_FAILURE",
72
- "connection_authentication_failure",
73
- ),
68
+ connection_with_attrs.EVENT_CONNECTION_AUTHENTICATION_FAILURE,
74
69
  lambda error=None, *_args: self._record_event(
75
70
  "connection_authentication_failure",
76
71
  adapter=self._adapter,
@@ -78,19 +73,11 @@ class ConnectionDiagnostics:
78
73
  ),
79
74
  )
80
75
  on_event(
81
- getattr(
82
- connection,
83
- "EVENT_CONNECTION_ENCRYPTION_CHANGE",
84
- "connection_encryption_change",
85
- ),
76
+ connection_with_attrs.EVENT_CONNECTION_ENCRYPTION_CHANGE,
86
77
  lambda *_args: self._record_connection_encryption_change(connection),
87
78
  )
88
79
  on_event(
89
- getattr(
90
- connection,
91
- "EVENT_CONNECTION_ENCRYPTION_FAILURE",
92
- "connection_encryption_failure",
93
- ),
80
+ connection_with_attrs.EVENT_CONNECTION_ENCRYPTION_FAILURE,
94
81
  lambda error=None, *_args: self._record_event(
95
82
  "connection_encryption_failure",
96
83
  adapter=self._adapter,
@@ -98,23 +85,19 @@ class ConnectionDiagnostics:
98
85
  ),
99
86
  )
100
87
  on_event(
101
- getattr(
102
- connection,
103
- "EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH",
104
- "connection_encryption_key_refresh",
105
- ),
88
+ connection_with_attrs.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
106
89
  lambda *_args: self._record_connection_encryption_refresh(connection),
107
90
  )
108
91
  on_event(
109
- getattr(connection, "EVENT_LINK_KEY", "link_key"),
92
+ connection_with_attrs.EVENT_LINK_KEY,
110
93
  lambda *_args: self._record_event("link_key_available", adapter=self._adapter),
111
94
  )
112
95
  on_event(
113
- getattr(connection, "EVENT_MODE_CHANGE", "mode_change"),
96
+ connection_with_attrs.EVENT_MODE_CHANGE,
114
97
  lambda *_args: self._record_classic_mode_change(connection),
115
98
  )
116
99
  on_event(
117
- getattr(connection, "EVENT_MODE_CHANGE_FAILURE", "mode_change_failure"),
100
+ connection_with_attrs.EVENT_MODE_CHANGE_FAILURE,
118
101
  lambda status=None, *_args: self._record_event(
119
102
  "classic_mode_change_failure",
120
103
  adapter=self._adapter,
@@ -179,7 +162,8 @@ def register_connection_request_bridge(
179
162
  record_event: EventRecorder,
180
163
  ) -> None:
181
164
  """Wrap Bumble's connection request callback while avoiding deprecated sync APIs."""
182
- original_connection_request = cast("Any", device).on_connection_request
165
+ device_with_attrs = cast("Any", device)
166
+ original_connection_request = device_with_attrs.on_connection_request
183
167
 
184
168
  def on_connection_request(
185
169
  bd_addr: object,
@@ -201,7 +185,6 @@ def register_connection_request_bridge(
201
185
  link_type,
202
186
  )
203
187
 
204
- device_with_attrs = cast("Any", device)
205
188
  device_with_attrs.on_connection_request = on_connection_request
206
189
  replace_host_connection_request_listener(
207
190
  device,
@@ -267,24 +250,18 @@ def call_connection_request_without_deprecated_sync_command(
267
250
  link_type: int,
268
251
  ) -> None:
269
252
  """Run Bumble's connection request handler without its deprecated sync helper."""
270
- host = getattr(device, "host", None)
271
- send_async_command = getattr(host, "send_async_command", None)
272
- if host is None or not callable(send_async_command):
273
- connection_request(bd_addr, class_of_device, link_type)
274
- return
253
+ device_with_attrs = cast("Any", device)
254
+ host = device_with_attrs.host
275
255
 
276
256
  from bumble import utils # noqa: PLC0415
277
257
 
278
258
  missing = object()
279
- host_dict = getattr(host, "__dict__", None)
280
- previous_instance_attr = (
281
- host_dict.get("send_command_sync", missing) if isinstance(host_dict, dict) else missing
282
- )
259
+ previous_instance_attr = host.__dict__.get("send_command_sync", missing)
283
260
 
284
261
  def send_command_sync(command: object) -> None:
285
- utils.AsyncRunner.spawn(send_async_command(command))
262
+ utils.AsyncRunner.spawn(host.send_async_command(command))
286
263
 
287
- host_with_attrs = cast("Any", host)
264
+ host_with_attrs = host
288
265
  host_with_attrs.send_command_sync = send_command_sync
289
266
  try:
290
267
  connection_request(bd_addr, class_of_device, link_type)
@@ -300,12 +277,9 @@ def replace_host_connection_request_listener(
300
277
  original_connection_request: Callable[[object, int, int], None],
301
278
  replacement_connection_request: Callable[[object, int, int], None],
302
279
  ) -> None:
303
- host = getattr(device, "host", None)
304
- remove_listener = getattr(host, "remove_listener", None)
305
- on = getattr(host, "on", None)
306
- if not callable(remove_listener) or not callable(on):
307
- return
280
+ device_with_attrs = cast("Any", device)
281
+ host = device_with_attrs.host
308
282
 
309
283
  with suppress(KeyError, ValueError):
310
- remove_listener("connection_request", original_connection_request)
311
- on("connection_request", replacement_connection_request)
284
+ host.remove_listener("connection_request", original_connection_request)
285
+ host.on("connection_request", replacement_connection_request)
@@ -16,7 +16,7 @@ _ADDRESS_PATTERN = re.compile(r"(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")
16
16
  _RESERVED_INQUIRY_LAP_MIN = 0x9E8B00
17
17
  _RESERVED_INQUIRY_LAP_MAX = 0x9E8B3F
18
18
  _PROFILE_FORMAT = "swbt.profile"
19
- _PROFILE_SCHEMA_VERSION = 1
19
+ _PROFILE_SCHEMA_VERSION = 2
20
20
  _PROFILE_LOCAL_ADDRESS_IDENTITY_KIND = "exp-local-address"
21
21
  _PROFILE_ADAPTER_DEFAULT_IDENTITY_KIND = "adapter-default"
22
22
  _CONTROLLER_KINDS_BY_PROFILE_VALUE: dict[str, ControllerKind] = {
@@ -79,7 +79,7 @@ class LocalAddress:
79
79
 
80
80
  @dataclass(frozen=True)
81
81
  class PairingProfile:
82
- """Validated version 1 envelope for one controller shape and its pairing data."""
82
+ """Validated version 2 envelope for one controller shape and its pairing data."""
83
83
 
84
84
  local_address: LocalAddress | None
85
85
  key_store_namespaces: KeyStoreNamespaces
@@ -97,14 +97,7 @@ class PairingProfile:
97
97
  address = None if local_address is None else str(local_address)
98
98
  profile = cls(
99
99
  local_address=local_address,
100
- key_store_namespaces=(
101
- {}
102
- if address is None
103
- else {
104
- address: {},
105
- f"swbt.previous::{address}": {},
106
- }
107
- ),
100
+ key_store_namespaces=({} if address is None else {address: {}}),
108
101
  controller_kind=controller_kind,
109
102
  )
110
103
  target = Path(path)
@@ -200,7 +193,10 @@ class PairingProfile:
200
193
  if payload.get("format") != _PROFILE_FORMAT:
201
194
  _invalid_profile("profile format is unsupported")
202
195
  if payload.get("schema_version") != _PROFILE_SCHEMA_VERSION:
203
- _invalid_profile("profile schema_version is unsupported")
196
+ _invalid_profile(
197
+ "profile schema_version is unsupported; create a new schema v2 profile "
198
+ "and pair again"
199
+ )
204
200
  controller_kind_value = payload.get("controller_kind")
205
201
  if not isinstance(controller_kind_value, str):
206
202
  _invalid_profile("profile controller_kind is unsupported")
swbt/transport/bumble.py CHANGED
@@ -132,6 +132,12 @@ class _BumbleHidRuntime(Protocol):
132
132
  def send_data(self, data: bytes) -> None:
133
133
  """Send an interrupt-channel HID data message."""
134
134
 
135
+ def register_set_report_cb(
136
+ self,
137
+ callback: Callable[[int, int, int, bytes], _BumbleGetSetStatus],
138
+ ) -> None:
139
+ """Register a HID SET_REPORT callback."""
140
+
135
141
  async def connect_control_channel(self) -> None:
136
142
  """Request control-channel L2CAP connection."""
137
143
 
@@ -455,10 +461,6 @@ class BumbleHidTransport:
455
461
  self._register_set_report_callback(hid_device)
456
462
 
457
463
  def _register_set_report_callback(self, hid_device: _BumbleHidRuntime) -> None:
458
- register_set_report = getattr(hid_device, "register_set_report_cb", None)
459
- if not callable(register_set_report):
460
- return
461
-
462
464
  def on_set_report(
463
465
  report_id: int,
464
466
  report_type: int,
@@ -475,7 +477,7 @@ class BumbleHidTransport:
475
477
  return _BumbleGetSetStatus(status=HID_GET_SET_UNSUPPORTED_REQUEST)
476
478
  return _BumbleGetSetStatus(status=HID_GET_SET_SUCCESS)
477
479
 
478
- register_set_report(on_set_report)
480
+ hid_device.register_set_report_cb(on_set_report)
479
481
 
480
482
  def _register_device_callbacks(self, device: _BumbleDeviceRuntime) -> None:
481
483
  device.on(device.EVENT_CONNECTION, self._handle_device_connection)
@@ -840,8 +842,7 @@ def _device_info_bluetooth_address_from_bumble_address(address: object) -> bytes
840
842
  """Return Device Info address bytes from a Bumble address object."""
841
843
  if address is None:
842
844
  return None
843
- to_string = getattr(address, "to_string", None)
844
- address_text = str(to_string(False)) if callable(to_string) else str(address)
845
+ address_text = str(cast("Any", address).to_string(False))
845
846
  address_text = address_text.split("/", 1)[0]
846
847
  parts = address_text.split(":")
847
848
  if len(parts) != 6 or any(len(part) != 2 for part in parts):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swbt-python
3
- Version: 0.5.2
3
+ Version: 0.5.3
4
4
  Summary: Python library for presenting an NX-compatible virtual Bluetooth HID input device.
5
5
  Keywords: bluetooth,bluetooth-hid,controller,gamepad,hid,nx
6
6
  Author: niart120
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Topic :: Software Development :: Libraries
20
20
  Classifier: Typing :: Typed
21
- Requires-Dist: bumble>=0.0.230,<0.0.231
21
+ Requires-Dist: bumble==0.0.233
22
22
  Maintainer: niart120
23
23
  Maintainer-email: niart120 <38847256+niart120@users.noreply.github.com>
24
24
  Requires-Python: >=3.12
@@ -34,20 +34,20 @@ swbt/report_loop.py,sha256=jEnwlxdWQgPwbIXvJfyhgMaJdrGYLNYzlZNotOMKiKw,7853
34
34
  swbt/state_store.py,sha256=jE1TeNpqynbVpxFp0Xt3m1Dfl1LUKR-uqV4f14qntuM,1402
35
35
  swbt/transport/__init__.py,sha256=b_Vqsg0k_g7XclN9XHQrcJxqCwGIklxG5NqzISal4_I,68
36
36
  swbt/transport/_adapter_identity.py,sha256=xOECQBlGAZJoyKrfxrIPt88kXoksHWmWzzVo1-x5XmA,6621
37
- swbt/transport/_bumble_acl.py,sha256=RnueTwEgAd0w3YgxnXkw0AKQXMeECIRope96W-xQ1K8,1425
37
+ swbt/transport/_bumble_acl.py,sha256=tZalNxNSKEsTB6WD4wEOyqEY-Td6s5wg2s_3Ve5UOqI,653
38
38
  swbt/transport/_bumble_hidp.py,sha256=mL_eT-otopHzg5G0p7uhqTYsqSuDwdXAiAwakQiHbCY,835
39
- swbt/transport/_bumble_key_store.py,sha256=j5fQtmnxlVk6DBPvdu7LIqu2-QCyGrvs1WoZZIj1Z-E,7986
40
- swbt/transport/_bumble_lifecycle.py,sha256=DHEMbZOfRLHD9jF62c2zLAn-TuFLk-8WzE1OdTSxoKI,11187
39
+ swbt/transport/_bumble_key_store.py,sha256=d7IJ9537QRruvy4aOyVtK7RsOOnjuJ7hKmiYTt7gC_I,6938
40
+ swbt/transport/_bumble_lifecycle.py,sha256=9NgXzyW3g2s91gPcsDuyzS9-rAp64ahDKk9fb3VJLDg,10174
41
41
  swbt/transport/_bumble_sdp.py,sha256=RtVgNI3EFi0-cIbiNqrLUi0A2Ju513kF-JWmFlkuvi4,9254
42
42
  swbt/transport/_bumble_usb_devices.py,sha256=soAKiHTzeeTYdb7M357yaac-RxG2c_BiKWH9NK9zJFY,894
43
43
  swbt/transport/_csr_bd_addr.py,sha256=FNc7y9SvEXhHyChZ8I4zgC-LyduiddLqt-HM_Ux2QXA,7949
44
44
  swbt/transport/_csr_bd_addr_harness.py,sha256=aSuFCVbneRENveN_b3CuamD7R9Wqf3CKRNU6Zcdxvpc,10297
45
- swbt/transport/_pairing_profile.py,sha256=ure-PK8gCnAtW_lRFjZgQyuKxYwP0fAOJpryHZR6pnM,9400
45
+ swbt/transport/_pairing_profile.py,sha256=95tISeYHnllHfFpNs0oBsaYb5uAffXfi9eFkjb-EeFQ,9342
46
46
  swbt/transport/base.py,sha256=eI0hs1wTwJf2ppzqda4ok-tUXF1hE9dwpb0p-lpvwmo,5163
47
- swbt/transport/bumble.py,sha256=ntbaXuWRB5_PJMlN52pVWrgIMg4JK8S9jhv65T9dHmo,33435
47
+ swbt/transport/bumble.py,sha256=VufUEo4aUeVebSFvZPJaVe_G7nNz5fKQMCbjgqmBbRs,33414
48
48
  swbt/transport/fake.py,sha256=4O5zlsg1TO61Ai54FGCn_xM8TEA9yi938Fso-XHdh1w,12891
49
- swbt_python-0.5.2.dist-info/licenses/LICENSE,sha256=FzzUoYjuIh5DzpeaujIbDUTylYnyzcHRVU-_ZMyowyk,1065
50
- swbt_python-0.5.2.dist-info/WHEEL,sha256=uOqnPWqgFlbov4NeTCercq7cBQ2UN7xh5fiW55lOnAg,81
51
- swbt_python-0.5.2.dist-info/entry_points.txt,sha256=YYQc7VgoSy74iZ6s8Qr6iJpnvFoDpp8G4-v0KJH5TGo,48
52
- swbt_python-0.5.2.dist-info/METADATA,sha256=w2O422wacUqZ5_ImxW4aSNfEeMKSPW2CetEbau6LlKU,5911
53
- swbt_python-0.5.2.dist-info/RECORD,,
49
+ swbt_python-0.5.3.dist-info/licenses/LICENSE,sha256=FzzUoYjuIh5DzpeaujIbDUTylYnyzcHRVU-_ZMyowyk,1065
50
+ swbt_python-0.5.3.dist-info/WHEEL,sha256=uOqnPWqgFlbov4NeTCercq7cBQ2UN7xh5fiW55lOnAg,81
51
+ swbt_python-0.5.3.dist-info/entry_points.txt,sha256=YYQc7VgoSy74iZ6s8Qr6iJpnvFoDpp8G4-v0KJH5TGo,48
52
+ swbt_python-0.5.3.dist-info/METADATA,sha256=W59IobyxaCz0_nIchUTIqNv24T9XjR9d7CuarGeItW0,5902
53
+ swbt_python-0.5.3.dist-info/RECORD,,