bdo-toolkit 1.0.0__tar.gz → 1.0.1__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.
- {bdo_toolkit-1.0.0/src/bdo_toolkit.egg-info → bdo_toolkit-1.0.1}/PKG-INFO +6 -5
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/README.md +3 -3
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/pyproject.toml +5 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_async_sessions.py +13 -18
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_capture_runtime.py +15 -17
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_framing.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_protocol.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_storage_hydration.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_version.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/calibration.py +11 -13
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/capture.py +18 -22
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/character_state.py +4 -5
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/events.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/filters.py +2 -5
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/origin_learning.py +3 -3
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/remote_profiles.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_discovery.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/async_session.py +10 -18
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/models.py +1 -1
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/session.py +5 -6
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1/src/bdo_toolkit.egg-info}/PKG-INFO +6 -5
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_async_sessions.py +1 -4
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_calibration_lifecycle.py +4 -5
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_capture_runtime.py +4 -5
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_session.py +13 -16
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/LICENSE +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/setup.cfg +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/__init__.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_capture_backend.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_capture_options.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_deposit_origin.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_engine.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_profile_runtime.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_reassembly.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_specs.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/_storage_destination_validation.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/cli.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/diagnostics.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/item_state.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/profiles.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/py.typed +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/__init__.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_constants.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_detail_learning.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_details.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_live_tracker.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_replay_capture.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_result.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_scanner.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/_validation.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/solare/replay.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit/writers.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit.egg-info/SOURCES.txt +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit.egg-info/dependency_links.txt +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit.egg-info/entry_points.txt +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit.egg-info/requires.txt +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/src/bdo_toolkit.egg-info/top_level.txt +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_calibration.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_calibration_conveniences.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_calibration_retention.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_character_state.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_cli.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_decoder_diagnostics.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_deposit_origin.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_direction.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_dynamic_storage_calibration_matrix.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_engine.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_examples_and_writers.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_explicit_profile.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_fixture_regressions.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_hardening.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_initial_load_storage.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_inventory_snapshots.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_item_state.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_live_capture_session.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_origin_learning.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_reassembly.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_remote_profiles.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_api.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_details.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_learning.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_learning_adversarial.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_real_captures.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_solare_replay.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_storage_destination_validation.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_storage_events.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_storage_hydration_tracker.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_storage_unknown_destination_calibration.py +0 -0
- {bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_toolkit_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bdo-toolkit
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Passive, read-only BDO packet parsing toolkit for app developers.
|
|
5
5
|
Author: bdo-toolkit contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -12,9 +12,10 @@ Keywords: bdo,packets,parse,pcap,passive
|
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
17
|
Classifier: Typing :: Typed
|
|
17
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.14
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
License-File: LICENSE
|
|
20
21
|
Requires-Dist: scapy>=2.5
|
|
@@ -27,7 +28,7 @@ Dynamic: license-file
|
|
|
27
28
|
# bdo-toolkit
|
|
28
29
|
|
|
29
30
|
[](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
|
|
30
|
-
[](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
|
|
31
32
|

|
|
32
33
|
[](https://ychwu.github.io/bdo-toolkit/#stability)
|
|
33
34
|
[](https://github.com/ychwu/bdo-toolkit/blob/main/LICENSE)
|
|
@@ -62,7 +63,7 @@ services is unknown.
|
|
|
62
63
|
python -m pip install bdo-toolkit
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
Python 3.
|
|
66
|
+
Python 3.14 or newer is required. Before live capture, complete
|
|
66
67
|
[Installation & setup](https://ychwu.github.io/bdo-toolkit/#capture-foundation),
|
|
67
68
|
including Npcap on Windows and permission to capture on the selected interface.
|
|
68
69
|
Offline PCAP and PCAPNG replay does not require Npcap.
|
|
@@ -134,7 +135,7 @@ python -m pip wheel . --no-deps --wheel-dir dist
|
|
|
134
135
|
```
|
|
135
136
|
|
|
136
137
|
CI runs tests, type checking, wheel construction, and a CLI smoke test on
|
|
137
|
-
Ubuntu and Windows with Python 3.
|
|
138
|
+
Ubuntu and Windows with Python 3.14. Tests that require private
|
|
138
139
|
game-session captures skip when those local fixtures are absent.
|
|
139
140
|
|
|
140
141
|
## License
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# bdo-toolkit
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
|
|
4
|
-
[](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
|
|
5
5
|

|
|
6
6
|
[](https://ychwu.github.io/bdo-toolkit/#stability)
|
|
7
7
|
[](https://github.com/ychwu/bdo-toolkit/blob/main/LICENSE)
|
|
@@ -36,7 +36,7 @@ services is unknown.
|
|
|
36
36
|
python -m pip install bdo-toolkit
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Python 3.
|
|
39
|
+
Python 3.14 or newer is required. Before live capture, complete
|
|
40
40
|
[Installation & setup](https://ychwu.github.io/bdo-toolkit/#capture-foundation),
|
|
41
41
|
including Npcap on Windows and permission to capture on the selected interface.
|
|
42
42
|
Offline PCAP and PCAPNG replay does not require Npcap.
|
|
@@ -108,7 +108,7 @@ python -m pip wheel . --no-deps --wheel-dir dist
|
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
CI runs tests, type checking, wheel construction, and a CLI smoke test on
|
|
111
|
-
Ubuntu and Windows with Python 3.
|
|
111
|
+
Ubuntu and Windows with Python 3.14. Tests that require private
|
|
112
112
|
game-session captures skip when those local fixtures are absent.
|
|
113
113
|
|
|
114
114
|
## License
|
|
@@ -7,7 +7,7 @@ name = "bdo-toolkit"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Passive, read-only BDO packet parsing toolkit for app developers."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.14"
|
|
11
11
|
dependencies = [
|
|
12
12
|
"scapy>=2.5",
|
|
13
13
|
]
|
|
@@ -21,6 +21,7 @@ classifiers = [
|
|
|
21
21
|
"Development Status :: 5 - Production/Stable",
|
|
22
22
|
"Operating System :: OS Independent",
|
|
23
23
|
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
24
25
|
"Programming Language :: Python :: 3 :: Only",
|
|
25
26
|
"Typing :: Typed",
|
|
26
27
|
]
|
|
@@ -47,6 +48,9 @@ where = ["src"]
|
|
|
47
48
|
[tool.pytest.ini_options]
|
|
48
49
|
testpaths = ["tests"]
|
|
49
50
|
|
|
51
|
+
[tool.mypy]
|
|
52
|
+
python_version = "3.14"
|
|
53
|
+
|
|
50
54
|
[tool.setuptools.package-data]
|
|
51
55
|
bdo_toolkit = ["py.typed"]
|
|
52
56
|
|
|
@@ -14,7 +14,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
14
14
|
from functools import partial
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from types import TracebackType
|
|
17
|
-
from typing import AsyncIterator, Callable, Optional
|
|
17
|
+
from typing import AsyncIterator, Callable, Optional
|
|
18
18
|
|
|
19
19
|
from ._capture_runtime import CaptureEndpoint, _attach_cleanup_owner
|
|
20
20
|
from ._capture_options import LiveCaptureOptions, PacketCaptureOptions
|
|
@@ -33,10 +33,7 @@ from .origin_learning import CompanionObservation
|
|
|
33
33
|
from .profiles import OpcodeProfile
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async def _wait_ignoring_cancellation(future: asyncio.Future[T]) -> T:
|
|
36
|
+
async def _wait_ignoring_cancellation[T](future: asyncio.Future[T]) -> T:
|
|
40
37
|
"""Wait for an already-started operation, even after caller cancellation."""
|
|
41
38
|
|
|
42
39
|
while not future.done():
|
|
@@ -51,14 +48,14 @@ async def _wait_ignoring_cancellation(future: asyncio.Future[T]) -> T:
|
|
|
51
48
|
return future.result()
|
|
52
49
|
|
|
53
50
|
|
|
54
|
-
async def _await_preserving_future(future: asyncio.Future[T]) -> T:
|
|
51
|
+
async def _await_preserving_future[T](future: asyncio.Future[T]) -> T:
|
|
55
52
|
"""Await without cancelling or wrapping the submitted worker future."""
|
|
56
53
|
|
|
57
54
|
await asyncio.wait((future,))
|
|
58
55
|
return future.result()
|
|
59
56
|
|
|
60
57
|
|
|
61
|
-
def _thread_task(function: Callable[[], T]) -> asyncio.Task[T]:
|
|
58
|
+
def _thread_task[T](function: Callable[[], T]) -> asyncio.Task[T]:
|
|
62
59
|
return asyncio.create_task(asyncio.to_thread(function))
|
|
63
60
|
|
|
64
61
|
|
|
@@ -143,7 +140,7 @@ class AsyncLiveCaptureSession:
|
|
|
143
140
|
|
|
144
141
|
self._session.raise_if_failed()
|
|
145
142
|
|
|
146
|
-
def _submit(self, function: Callable[[], T]) -> asyncio.Future[T]:
|
|
143
|
+
def _submit[T](self, function: Callable[[], T]) -> asyncio.Future[T]:
|
|
147
144
|
if self._executor_closed:
|
|
148
145
|
raise RuntimeError("async live capture session is already closed")
|
|
149
146
|
loop = asyncio.get_running_loop()
|
|
@@ -380,11 +377,10 @@ class AsyncLiveCaptureSession:
|
|
|
380
377
|
self,
|
|
381
378
|
context="async live item capture context",
|
|
382
379
|
)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
380
|
+
exc_value.add_note(
|
|
381
|
+
"async live item capture context cleanup also failed: "
|
|
382
|
+
f"{cleanup_error!r}"
|
|
383
|
+
)
|
|
388
384
|
|
|
389
385
|
|
|
390
386
|
class AsyncCalibrationSession:
|
|
@@ -644,8 +640,7 @@ class AsyncCalibrationSession:
|
|
|
644
640
|
self,
|
|
645
641
|
context="async live calibration context",
|
|
646
642
|
)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
)
|
|
643
|
+
exc_value.add_note(
|
|
644
|
+
"async live calibration context cleanup also failed: "
|
|
645
|
+
f"{cleanup_error!r}"
|
|
646
|
+
)
|
|
@@ -64,18 +64,17 @@ def _attach_cleanup_owner(
|
|
|
64
64
|
# Built-in and ordinary application exceptions expose __dict__. Keep
|
|
65
65
|
# the original failure identity even for an exotic immutable subtype.
|
|
66
66
|
pass
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
67
|
+
if attached:
|
|
68
|
+
error.add_note(
|
|
69
|
+
f"{context} cleanup is incomplete; retry stop() on "
|
|
70
|
+
"exception.cleanup_owner"
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
error.add_note(
|
|
74
|
+
f"{context} cleanup is incomplete; this exception subtype "
|
|
75
|
+
"cannot expose cleanup_owner, so retain and stop the original "
|
|
76
|
+
"session object"
|
|
77
|
+
)
|
|
79
78
|
|
|
80
79
|
|
|
81
80
|
@dataclass(frozen=True)
|
|
@@ -456,11 +455,10 @@ class LivePacketCapture:
|
|
|
456
455
|
# Startup remains the primary failure, but incomplete
|
|
457
456
|
# cleanup must be explicit in its traceback and remains
|
|
458
457
|
# retryable through stop().
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
)
|
|
458
|
+
exc.add_note(
|
|
459
|
+
"live capture startup cleanup also failed: "
|
|
460
|
+
f"{cleanup_error!r}"
|
|
461
|
+
)
|
|
464
462
|
if not self._cleanup_incomplete:
|
|
465
463
|
self._capture = None
|
|
466
464
|
self._capture_socket = None
|
|
@@ -1021,11 +1021,10 @@ class CalibrationSession:
|
|
|
1021
1021
|
try:
|
|
1022
1022
|
manager.finish()
|
|
1023
1023
|
except BaseException as cleanup_error:
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
)
|
|
1024
|
+
exc.add_note(
|
|
1025
|
+
"calibration flow cleanup also failed: "
|
|
1026
|
+
f"{cleanup_error!r}"
|
|
1027
|
+
)
|
|
1029
1028
|
self._manager = None
|
|
1030
1029
|
self._capture = None
|
|
1031
1030
|
raise
|
|
@@ -1187,11 +1186,10 @@ class CalibrationSession:
|
|
|
1187
1186
|
self,
|
|
1188
1187
|
context="live calibration context",
|
|
1189
1188
|
)
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
)
|
|
1189
|
+
exc_value.add_note(
|
|
1190
|
+
"calibration context cleanup also failed: "
|
|
1191
|
+
f"{cleanup_error!r}"
|
|
1192
|
+
)
|
|
1195
1193
|
|
|
1196
1194
|
|
|
1197
1195
|
def calibrate_live(
|
|
@@ -3029,7 +3027,7 @@ def _calibration_source(options: _Options, action: str) -> str:
|
|
|
3029
3027
|
|
|
3030
3028
|
def _iso_timestamp(timestamp: float) -> str:
|
|
3031
3029
|
return (
|
|
3032
|
-
dt.datetime.fromtimestamp(timestamp, tz=dt.
|
|
3030
|
+
dt.datetime.fromtimestamp(timestamp, tz=dt.UTC)
|
|
3033
3031
|
.isoformat(timespec="seconds")
|
|
3034
3032
|
.replace("+00:00", "Z")
|
|
3035
3033
|
)
|
|
@@ -3037,7 +3035,7 @@ def _iso_timestamp(timestamp: float) -> str:
|
|
|
3037
3035
|
|
|
3038
3036
|
def _utc_now_text() -> str:
|
|
3039
3037
|
return (
|
|
3040
|
-
dt.datetime.now(tz=dt.
|
|
3038
|
+
dt.datetime.now(tz=dt.UTC)
|
|
3041
3039
|
.isoformat(timespec="seconds")
|
|
3042
3040
|
.replace("+00:00", "Z")
|
|
3043
3041
|
)
|
|
@@ -3173,7 +3171,7 @@ def _profile_dedupe_keys(data: dict[str, Any]) -> set[tuple[object, ...]]:
|
|
|
3173
3171
|
def _backup_path(path: Path) -> Path:
|
|
3174
3172
|
backup_dir = path.parent / "opcodes_backups"
|
|
3175
3173
|
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
3176
|
-
stamp = dt.datetime.now(tz=dt.
|
|
3174
|
+
stamp = dt.datetime.now(tz=dt.UTC).strftime("%Y%m%d%H%M%S%f")
|
|
3177
3175
|
candidate = backup_dir / f"{path.name}.bak.{stamp}"
|
|
3178
3176
|
suffix = 1
|
|
3179
3177
|
while candidate.exists():
|
|
@@ -52,9 +52,9 @@ from .profiles import (
|
|
|
52
52
|
|
|
53
53
|
_PACKET_WORKER_STOP = object()
|
|
54
54
|
_TARGET_FRAME_HISTORY_LIMIT = 4096
|
|
55
|
-
_TargetFrameKey = tuple[FlowKey, int, Optional[int], float, int, int]
|
|
55
|
+
type _TargetFrameKey = tuple[FlowKey, int, Optional[int], float, int, int]
|
|
56
56
|
_INVENTORY_BOUNDARY_COHORT_LIMIT = 64
|
|
57
|
-
_InventoryBoundaryKey = tuple[FlowKey, int, int]
|
|
57
|
+
type _InventoryBoundaryKey = tuple[FlowKey, int, int]
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
@dataclass
|
|
@@ -1111,11 +1111,10 @@ class LiveCaptureSession:
|
|
|
1111
1111
|
with self._state_lock:
|
|
1112
1112
|
self._cleanup_incomplete = True
|
|
1113
1113
|
for failure in cleanup_failures:
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
)
|
|
1114
|
+
error.add_note(
|
|
1115
|
+
"live item capture startup cleanup also failed: "
|
|
1116
|
+
f"{failure!r}"
|
|
1117
|
+
)
|
|
1119
1118
|
_attach_cleanup_owner(
|
|
1120
1119
|
error,
|
|
1121
1120
|
self,
|
|
@@ -1124,11 +1123,10 @@ class LiveCaptureSession:
|
|
|
1124
1123
|
return
|
|
1125
1124
|
|
|
1126
1125
|
for failure in cleanup_failures:
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
)
|
|
1126
|
+
error.add_note(
|
|
1127
|
+
"live item capture startup cleanup reported a fully "
|
|
1128
|
+
f"released error: {failure!r}"
|
|
1129
|
+
)
|
|
1132
1130
|
self._reset_after_failed_start()
|
|
1133
1131
|
|
|
1134
1132
|
def _reset_after_failed_start(self) -> None:
|
|
@@ -1586,11 +1584,10 @@ class LiveCaptureSession:
|
|
|
1586
1584
|
self,
|
|
1587
1585
|
context="live item capture context",
|
|
1588
1586
|
)
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
)
|
|
1587
|
+
exc_value.add_note(
|
|
1588
|
+
"live item capture context cleanup also failed: "
|
|
1589
|
+
f"{cleanup_error!r}"
|
|
1590
|
+
)
|
|
1594
1591
|
|
|
1595
1592
|
|
|
1596
1593
|
def capture_live(
|
|
@@ -1651,11 +1648,10 @@ def capture_live(
|
|
|
1651
1648
|
session,
|
|
1652
1649
|
context="timed live item capture startup",
|
|
1653
1650
|
)
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
)
|
|
1651
|
+
exc.add_note(
|
|
1652
|
+
"timed live item capture startup cleanup also "
|
|
1653
|
+
f"failed: {cleanup_error!r}"
|
|
1654
|
+
)
|
|
1659
1655
|
raise
|
|
1660
1656
|
try:
|
|
1661
1657
|
while True:
|
|
@@ -3244,11 +3244,10 @@ class CharacterLoadSession:
|
|
|
3244
3244
|
self,
|
|
3245
3245
|
context="character-load capture context",
|
|
3246
3246
|
)
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
)
|
|
3247
|
+
exc_value.add_note(
|
|
3248
|
+
"character-load context cleanup also failed: "
|
|
3249
|
+
f"{cleanup_error!r}"
|
|
3250
|
+
)
|
|
3252
3251
|
|
|
3253
3252
|
|
|
3254
3253
|
def format_character_state(
|
|
@@ -121,7 +121,7 @@ class BDOEvent:
|
|
|
121
121
|
def timestamp_iso(self) -> str:
|
|
122
122
|
"""Deterministic UTC ISO-8601 timestamp for serialization."""
|
|
123
123
|
return (
|
|
124
|
-
dt.datetime.fromtimestamp(self.timestamp, tz=dt.
|
|
124
|
+
dt.datetime.fromtimestamp(self.timestamp, tz=dt.UTC)
|
|
125
125
|
.isoformat(timespec="milliseconds")
|
|
126
126
|
.replace("+00:00", "Z")
|
|
127
127
|
)
|
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Iterable, Optional
|
|
6
|
+
from typing import Iterable, Optional
|
|
7
7
|
|
|
8
8
|
from .events import BDOEvent
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
T = TypeVar("T")
|
|
12
|
-
|
|
13
|
-
|
|
14
11
|
_ACTIVITY_EVENT_TYPES = frozenset(
|
|
15
12
|
{"loot_preview", "item_received", "storage_delta"}
|
|
16
13
|
)
|
|
@@ -19,7 +16,7 @@ _SNAPSHOT_RECORD_EVENT_TYPES = frozenset(
|
|
|
19
16
|
)
|
|
20
17
|
|
|
21
18
|
|
|
22
|
-
def _freeze(values: Optional[Iterable[T]]) -> Optional[frozenset[T]]:
|
|
19
|
+
def _freeze[T](values: Optional[Iterable[T]]) -> Optional[frozenset[T]]:
|
|
23
20
|
if values is None:
|
|
24
21
|
return None
|
|
25
22
|
if isinstance(values, (str, bytes)):
|
|
@@ -46,9 +46,9 @@ def _opcode_text(opcode: int) -> str:
|
|
|
46
46
|
|
|
47
47
|
def _utc_text(timestamp: Optional[float] = None) -> str:
|
|
48
48
|
value = (
|
|
49
|
-
dt.datetime.fromtimestamp(timestamp, tz=dt.
|
|
49
|
+
dt.datetime.fromtimestamp(timestamp, tz=dt.UTC)
|
|
50
50
|
if timestamp is not None
|
|
51
|
-
else dt.datetime.now(tz=dt.
|
|
51
|
+
else dt.datetime.now(tz=dt.UTC)
|
|
52
52
|
)
|
|
53
53
|
return value.isoformat(timespec="seconds").replace("+00:00", "Z")
|
|
54
54
|
|
|
@@ -752,7 +752,7 @@ def promote_origin_candidates(
|
|
|
752
752
|
def _unique_backup_path(path: Path) -> Path:
|
|
753
753
|
backup_dir = path.parent / "opcodes_backups"
|
|
754
754
|
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
755
|
-
stamp = dt.datetime.now(tz=dt.
|
|
755
|
+
stamp = dt.datetime.now(tz=dt.UTC).strftime("%Y%m%d%H%M%S%f")
|
|
756
756
|
return backup_dir / f"{path.name}.bak.{stamp}"
|
|
757
757
|
|
|
758
758
|
|
|
@@ -339,7 +339,7 @@ def _is_sha256(value: str) -> bool:
|
|
|
339
339
|
def _next_backup_path(path: Path) -> Path:
|
|
340
340
|
backup_dir = path.parent / "opcodes_backups"
|
|
341
341
|
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
342
|
-
stamp = dt.datetime.now(tz=dt.
|
|
342
|
+
stamp = dt.datetime.now(tz=dt.UTC).strftime("%Y%m%d%H%M%S%f")
|
|
343
343
|
candidate = backup_dir / f"{path.name}.bak.{stamp}"
|
|
344
344
|
suffix = 1
|
|
345
345
|
while candidate.exists():
|
|
@@ -8,7 +8,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
8
8
|
from functools import partial
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from types import TracebackType
|
|
11
|
-
from typing import AsyncIterator, Callable, Optional
|
|
11
|
+
from typing import AsyncIterator, Callable, Optional
|
|
12
12
|
|
|
13
13
|
from bdo_toolkit._capture_options import PacketCaptureOptions
|
|
14
14
|
from bdo_toolkit._capture_runtime import _attach_cleanup_owner
|
|
@@ -22,14 +22,7 @@ from .models import (
|
|
|
22
22
|
from .session import LiveSolareSession, _validate_timeout
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# Stay above the 15.625 ms monotonic-clock quantum used by Windows CPython
|
|
28
|
-
# 3.10 so terminal cleanup polling yields instead of busy-spinning.
|
|
29
|
-
_TERMINAL_SHUTDOWN_POLL_SECONDS = 0.05
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
async def _settle(future: asyncio.Future[T]) -> T:
|
|
25
|
+
async def _settle[T](future: asyncio.Future[T]) -> T:
|
|
33
26
|
while not future.done():
|
|
34
27
|
try:
|
|
35
28
|
await asyncio.wait((future,))
|
|
@@ -38,7 +31,7 @@ async def _settle(future: asyncio.Future[T]) -> T:
|
|
|
38
31
|
return future.result()
|
|
39
32
|
|
|
40
33
|
|
|
41
|
-
async def _await_preserving_future(future: asyncio.Future[T]) -> T:
|
|
34
|
+
async def _await_preserving_future[T](future: asyncio.Future[T]) -> T:
|
|
42
35
|
"""Await a worker future without cancelling it or creating a shield."""
|
|
43
36
|
|
|
44
37
|
await asyncio.wait((future,))
|
|
@@ -114,7 +107,7 @@ class AsyncLiveSolareSession:
|
|
|
114
107
|
def raise_if_failed(self) -> None:
|
|
115
108
|
self._session.raise_if_failed()
|
|
116
109
|
|
|
117
|
-
def _submit(self, function: Callable[[], T]) -> asyncio.Future[T]:
|
|
110
|
+
def _submit[T](self, function: Callable[[], T]) -> asyncio.Future[T]:
|
|
118
111
|
if self._closed:
|
|
119
112
|
raise RuntimeError("async live Solare session is already closed")
|
|
120
113
|
return asyncio.get_running_loop().run_in_executor(self._executor, function)
|
|
@@ -131,7 +124,7 @@ class AsyncLiveSolareSession:
|
|
|
131
124
|
|
|
132
125
|
async def close_after_worker_and_calls_settle() -> None:
|
|
133
126
|
while not self._session.stopped and not self._closed:
|
|
134
|
-
await asyncio.sleep(
|
|
127
|
+
await asyncio.sleep(0.01)
|
|
135
128
|
while not self._closed and (
|
|
136
129
|
self._start_active
|
|
137
130
|
or self._poll_active
|
|
@@ -141,7 +134,7 @@ class AsyncLiveSolareSession:
|
|
|
141
134
|
and not self._stop_future.done()
|
|
142
135
|
)
|
|
143
136
|
):
|
|
144
|
-
await asyncio.sleep(
|
|
137
|
+
await asyncio.sleep(0.01)
|
|
145
138
|
self._shutdown()
|
|
146
139
|
|
|
147
140
|
self._terminal_shutdown_task = asyncio.create_task(
|
|
@@ -434,11 +427,10 @@ class AsyncLiveSolareSession:
|
|
|
434
427
|
self,
|
|
435
428
|
context="async live Solare context",
|
|
436
429
|
)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
)
|
|
430
|
+
exc_value.add_note(
|
|
431
|
+
"async live Solare context cleanup also failed: "
|
|
432
|
+
f"{cleanup_error!r}"
|
|
433
|
+
)
|
|
442
434
|
finally:
|
|
443
435
|
if not self._session.cleanup_incomplete:
|
|
444
436
|
self._shutdown()
|
|
@@ -445,7 +445,7 @@ class SolareLeaderboardSnapshot:
|
|
|
445
445
|
@property
|
|
446
446
|
def observed_at_iso(self) -> str:
|
|
447
447
|
return (
|
|
448
|
-
dt.datetime.fromtimestamp(self.observed_at, tz=dt.
|
|
448
|
+
dt.datetime.fromtimestamp(self.observed_at, tz=dt.UTC)
|
|
449
449
|
.isoformat(timespec="milliseconds")
|
|
450
450
|
.replace("+00:00", "Z")
|
|
451
451
|
)
|
|
@@ -980,11 +980,10 @@ class LiveSolareSession:
|
|
|
980
980
|
self,
|
|
981
981
|
context="live Solare context",
|
|
982
982
|
)
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
)
|
|
983
|
+
exc_value.add_note(
|
|
984
|
+
"live Solare context cleanup also failed: "
|
|
985
|
+
f"{cleanup_error!r}"
|
|
986
|
+
)
|
|
988
987
|
|
|
989
988
|
|
|
990
989
|
def capture_solare_snapshot(
|
|
@@ -1043,7 +1042,7 @@ def capture_solare_snapshot(
|
|
|
1043
1042
|
session,
|
|
1044
1043
|
context="live Solare convenience wrapper",
|
|
1045
1044
|
)
|
|
1046
|
-
if cleanup_error is not exc
|
|
1045
|
+
if cleanup_error is not exc:
|
|
1047
1046
|
exc.add_note(
|
|
1048
1047
|
"live Solare convenience cleanup also failed: "
|
|
1049
1048
|
f"{cleanup_error!r}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bdo-toolkit
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Passive, read-only BDO packet parsing toolkit for app developers.
|
|
5
5
|
Author: bdo-toolkit contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -12,9 +12,10 @@ Keywords: bdo,packets,parse,pcap,passive
|
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
17
|
Classifier: Typing :: Typed
|
|
17
|
-
Requires-Python: >=3.
|
|
18
|
+
Requires-Python: >=3.14
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
License-File: LICENSE
|
|
20
21
|
Requires-Dist: scapy>=2.5
|
|
@@ -27,7 +28,7 @@ Dynamic: license-file
|
|
|
27
28
|
# bdo-toolkit
|
|
28
29
|
|
|
29
30
|
[](https://github.com/ychwu/bdo-toolkit/actions/workflows/ci.yml)
|
|
30
|
-
[](https://github.com/ychwu/bdo-toolkit/blob/main/pyproject.toml)
|
|
31
32
|

|
|
32
33
|
[](https://ychwu.github.io/bdo-toolkit/#stability)
|
|
33
34
|
[](https://github.com/ychwu/bdo-toolkit/blob/main/LICENSE)
|
|
@@ -62,7 +63,7 @@ services is unknown.
|
|
|
62
63
|
python -m pip install bdo-toolkit
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
Python 3.
|
|
66
|
+
Python 3.14 or newer is required. Before live capture, complete
|
|
66
67
|
[Installation & setup](https://ychwu.github.io/bdo-toolkit/#capture-foundation),
|
|
67
68
|
including Npcap on Windows and permission to capture on the selected interface.
|
|
68
69
|
Offline PCAP and PCAPNG replay does not require Npcap.
|
|
@@ -134,7 +135,7 @@ python -m pip wheel . --no-deps --wheel-dir dist
|
|
|
134
135
|
```
|
|
135
136
|
|
|
136
137
|
CI runs tests, type checking, wheel construction, and a CLI smoke test on
|
|
137
|
-
Ubuntu and Windows with Python 3.
|
|
138
|
+
Ubuntu and Windows with Python 3.14. Tests that require private
|
|
138
139
|
game-session captures skip when those local fixtures are absent.
|
|
139
140
|
|
|
140
141
|
## License
|
|
@@ -1015,10 +1015,7 @@ def test_async_calibration_cancelled_reuse_exposes_current_run_for_retry(
|
|
|
1015
1015
|
with pytest.raises(asyncio.CancelledError) as cancelled:
|
|
1016
1016
|
await starting
|
|
1017
1017
|
|
|
1018
|
-
|
|
1019
|
-
# boundary, which necessarily drops custom exception attributes.
|
|
1020
|
-
if hasattr(asyncio.CancelledError, "add_note"):
|
|
1021
|
-
assert cancelled.value.cleanup_owner is session
|
|
1018
|
+
assert cancelled.value.cleanup_owner is session
|
|
1022
1019
|
assert session.cleanup_incomplete
|
|
1023
1020
|
assert session.running
|
|
1024
1021
|
assert session.result is None
|
|
@@ -521,11 +521,10 @@ def test_context_cleanup_failure_is_retained_without_masking_block_error(
|
|
|
521
521
|
|
|
522
522
|
assert caught.value is block_failure
|
|
523
523
|
assert session.error is _RaisingStopSniffer.failure
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
)
|
|
524
|
+
assert any(
|
|
525
|
+
"context cleanup also failed" in note
|
|
526
|
+
for note in caught.value.__notes__
|
|
527
|
+
)
|
|
529
528
|
assert not session.running
|
|
530
529
|
|
|
531
530
|
|
|
@@ -513,11 +513,10 @@ def test_startup_failure_retains_an_unstopped_backend_for_cleanup_retry(
|
|
|
513
513
|
assert capture.running
|
|
514
514
|
assert capture._capture is sniffer
|
|
515
515
|
assert thread.join_calls == [runtime._CAPTURE_JOIN_TIMEOUT_SECONDS]
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
)
|
|
516
|
+
assert any(
|
|
517
|
+
"startup cleanup also failed" in note
|
|
518
|
+
for note in startup.value.__notes__
|
|
519
|
+
)
|
|
521
520
|
|
|
522
521
|
thread.alive = False
|
|
523
522
|
sniffer.running = False
|
|
@@ -426,11 +426,7 @@ def test_capture_convenience_exposes_hidden_owner_on_incomplete_cleanup(
|
|
|
426
426
|
|
|
427
427
|
assert failed.value is primary_failure
|
|
428
428
|
assert primary_failure.cleanup_owner is IncompleteSession.instances[-1]
|
|
429
|
-
|
|
430
|
-
assert any(
|
|
431
|
-
"cleanup also failed" in note
|
|
432
|
-
for note in primary_failure.__notes__
|
|
433
|
-
)
|
|
429
|
+
assert any("cleanup also failed" in note for note in primary_failure.__notes__)
|
|
434
430
|
|
|
435
431
|
|
|
436
432
|
def test_capture_convenience_allows_explicit_infinite_wait(
|
|
@@ -1677,10 +1673,10 @@ def test_async_finished_poll_closes_executor_after_callback_returns(
|
|
|
1677
1673
|
assert not session._closed
|
|
1678
1674
|
assert not session.stopped
|
|
1679
1675
|
release_callback.set()
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1676
|
+
for _ in range(200):
|
|
1677
|
+
if session.stopped and session._closed:
|
|
1678
|
+
break
|
|
1679
|
+
await asyncio.sleep(0.01)
|
|
1684
1680
|
assert session.stopped
|
|
1685
1681
|
assert session._closed
|
|
1686
1682
|
|
|
@@ -2061,15 +2057,16 @@ def test_async_cancellation_returns_after_bounded_incomplete_cleanup(
|
|
|
2061
2057
|
assert preserved.kind is SolareUpdateKind.FINISHED
|
|
2062
2058
|
|
|
2063
2059
|
release_callback.set()
|
|
2064
|
-
|
|
2060
|
+
for _ in range(200):
|
|
2061
|
+
if session.stopped:
|
|
2062
|
+
break
|
|
2063
|
+
await asyncio.sleep(0.01)
|
|
2065
2064
|
assert session.stopped
|
|
2066
2065
|
if operation == "poll":
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
asyncio.
|
|
2071
|
-
timeout=2.0,
|
|
2072
|
-
)
|
|
2066
|
+
for _ in range(200):
|
|
2067
|
+
if session._closed:
|
|
2068
|
+
break
|
|
2069
|
+
await asyncio.sleep(0.01)
|
|
2073
2070
|
assert session._closed
|
|
2074
2071
|
with pytest.raises(RuntimeError, match="worker cleanup is incomplete"):
|
|
2075
2072
|
await session.stop()
|
|
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
|
|
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
|
|
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
|
|
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
|
{bdo_toolkit-1.0.0 → bdo_toolkit-1.0.1}/tests/test_storage_unknown_destination_calibration.py
RENAMED
|
File without changes
|
|
File without changes
|