Skip to content

Commit 84346a0

Browse files
committed
Merge remote-tracking branch 'origin/main' into support-python-3.14
# Conflicts: # ci/test_custom_linters.py
2 parents d79bcdb + 39e5296 commit 84346a0

5 files changed

Lines changed: 81 additions & 68 deletions

File tree

ci/test_custom_linters.py

Lines changed: 60 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Custom lint tests."""
22

3-
import subprocess
4-
import sys
53
from pathlib import Path
64

75
import pytest
@@ -21,61 +19,52 @@ def _ci_patterns(*, repository_root: Path) -> set[str]:
2119
return ci_patterns
2220

2321

22+
class _CollectPlugin:
23+
"""Pytest plugin that records the node IDs of collected items."""
24+
25+
def __init__(self) -> None:
26+
"""Start with an empty set of collected node IDs."""
27+
self.collected: set[str] = set()
28+
29+
def pytest_itemcollected(self, item: pytest.Item) -> None:
30+
"""Record each collected item's node ID."""
31+
self.collected.add(item.nodeid)
32+
33+
2434
@beartype
25-
def _collect(
26-
*, ci_pattern: str, repository_root: Path
27-
) -> subprocess.CompletedProcess[str]:
28-
"""Run ``pytest --collect-only`` for ``ci_pattern`` in a fresh
29-
subprocess.
30-
31-
A real subprocess (not ``pytest.main``) is used so that plugin state
32-
-- notably ``pytest-beartype-tests`` re-wrapping the same test
33-
functions -- does not accumulate across iterations and trigger
34-
``Cannot stringify annotation containing string formatting`` under
35-
Python 3.14 deferred annotations.
36-
See https://github.com/adamtheturtle/pytest-beartype-tests/issues/30.
37-
"""
38-
return subprocess.run(
35+
def _tests_from_pattern(*, ci_pattern: str) -> set[str]:
36+
"""From a CI pattern, get all tests ``pytest`` would collect."""
37+
plugin = _CollectPlugin()
38+
pytest.main(
3939
args=[
40-
sys.executable,
41-
"-m",
42-
"pytest",
4340
"-q",
4441
"--collect-only",
4542
# Disable pytest-retry to avoid:
4643
# ```
4744
# ValueError: no option named 'filtered_exceptions'
4845
# ```
46+
# which causes the nested run to exit with INTERNAL_ERROR
47+
# before any items are collected.
4948
"-p",
5049
"no:pytest-retry",
50+
# Disable pytest-beartype-tests to avoid
51+
# https://github.com/beartype/beartype/issues/637 — wrapping
52+
# collected items with @beartype installs a buggy
53+
# __annotate_beartype__ closure on the underlying test
54+
# function, which crashes a subsequent nested collection on
55+
# Python 3.14.
56+
"-p",
57+
"no:pytest_beartype_tests",
5158
# Disable warnings to avoid many instances of:
5259
# ```
5360
# Unknown config option: retry_delay
5461
# ```
5562
"--disable-warnings",
5663
ci_pattern,
5764
],
58-
check=False,
59-
cwd=repository_root,
60-
capture_output=True,
61-
text=True,
65+
plugins=[plugin],
6266
)
63-
64-
65-
@beartype
66-
def _tests_from_pattern(*, ci_pattern: str, repository_root: Path) -> set[str]:
67-
"""From a CI pattern, get all tests ``pytest`` would collect."""
68-
result = _collect(
69-
ci_pattern=ci_pattern,
70-
repository_root=repository_root,
71-
)
72-
tests: set[str] = set()
73-
for line in result.stdout.splitlines():
74-
# We filter empty lines and lines which look like
75-
# "9 tests collected in 0.01s".
76-
if line and "collected in" not in line:
77-
tests.add(line)
78-
return tests
67+
return plugin.collected
7968

8069

8170
def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None:
@@ -84,45 +73,53 @@ def test_ci_patterns_valid(request: pytest.FixtureRequest) -> None:
8473
test in
8574
the test suite.
8675
"""
87-
repository_root = request.config.rootpath
88-
ci_patterns = _ci_patterns(repository_root=repository_root)
76+
ci_patterns = _ci_patterns(repository_root=request.config.rootpath)
8977

9078
for ci_pattern in ci_patterns:
91-
result = _collect(
92-
ci_pattern=ci_pattern,
93-
repository_root=repository_root,
94-
)
95-
message = (
96-
f'"{ci_pattern}" does not match any tests.\n'
97-
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
79+
collect_only_result = pytest.main(
80+
args=[
81+
"--collect-only",
82+
ci_pattern,
83+
# Disable pytest-retry to avoid:
84+
# ```
85+
# ValueError: no option named 'filtered_exceptions'
86+
# ````
87+
"-p",
88+
"no:pytest-retry",
89+
# Disable pytest-beartype-tests to avoid
90+
# https://github.com/beartype/beartype/issues/637 —
91+
# wrapping collected items with @beartype installs a
92+
# buggy __annotate_beartype__ closure on the underlying
93+
# test function, which crashes a subsequent nested
94+
# collection on Python 3.14.
95+
"-p",
96+
"no:pytest_beartype_tests",
97+
# Disable warnings to avoid many instances of:
98+
# ```
99+
# Unknown config option: retry_delay
100+
# ```
101+
"--disable-warnings",
102+
],
98103
)
99-
assert result.returncode == 0, message
104+
105+
message = f'"{ci_pattern}" does not match any tests.'
106+
assert collect_only_result == 0, message
100107

101108

102109
def test_tests_collected_once(request: pytest.FixtureRequest) -> None:
103110
"""Each test in the test suite is collected exactly once.
104111
105112
This does not necessarily mean that they are run - they may be skipped.
106113
"""
107-
repository_root = request.config.rootpath
108-
ci_patterns = _ci_patterns(repository_root=repository_root)
109-
all_tests = _tests_from_pattern(
110-
ci_pattern=".",
111-
repository_root=repository_root,
112-
)
114+
ci_patterns = _ci_patterns(repository_root=request.config.rootpath)
115+
all_tests = _tests_from_pattern(ci_pattern=".")
113116
assert all_tests
114117
tests_to_patterns: dict[str, set[str]] = {}
115118

116119
for pattern in ci_patterns:
117-
tests = _tests_from_pattern(
118-
ci_pattern=pattern,
119-
repository_root=repository_root,
120-
)
120+
tests = _tests_from_pattern(ci_pattern=pattern)
121121
for test in tests:
122-
if test in tests_to_patterns:
123-
tests_to_patterns[test].add(pattern)
124-
else:
125-
tests_to_patterns[test] = {pattern}
122+
tests_to_patterns.setdefault(test, set()).add(pattern)
126123

127124
for test_name, patterns in tests_to_patterns.items():
128125
message = (

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ optional-dependencies.dev = [
7373
"pylint-per-file-ignores==3.2.1",
7474
"pyproject-fmt==2.21.1",
7575
"pyrefly==0.62.0",
76-
"pyright==1.1.408",
76+
"pyright==1.1.409",
7777
"pyroma==5.0.1",
7878
"pytest==9.0.3",
7979
"pytest-beartype-tests==2026.4.20",
@@ -380,7 +380,7 @@ run.omit = [
380380
"src/mock_vws/_flask_server/healthcheck.py",
381381
]
382382
run.parallel = true
383-
run.source = [ "src/", "tests/" ]
383+
run.source = [ "ci/", "src/", "tests/" ]
384384
report.exclude_also = [
385385
"class .*\\bProtocol\\):",
386386
"if TYPE_CHECKING:",

spelling_private_dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ reportAssignmentType
8989
reportAttributeAccessIssue
9090
reportGeneralTypeIssues
9191
reportMissingTypeStubs
92+
reportPrivateImportUsage
9293
reportUnknownArgumentType
9394
reportUnknownMemberType
9495
reportUnknownVariableType

src/mock_vws/image_matchers.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ def __call__(
8181
second_image_resized = second_image.resize(size=target_size)
8282

8383
first_image_np = np.array(object=first_image_resized, dtype=np.float32)
84-
first_image_tensor = torch.tensor(data=first_image_np).float() / 255
84+
first_image_tensor = (
85+
torch.tensor( # pyright: ignore[reportPrivateImportUsage]
86+
data=first_image_np,
87+
).float()
88+
/ 255
89+
)
8590
first_image_tensor = first_image_tensor.view(
8691
first_image_resized.size[1],
8792
first_image_resized.size[0],
@@ -92,7 +97,12 @@ def __call__(
9297
object=second_image_resized,
9398
dtype=np.float32,
9499
)
95-
second_image_tensor = torch.tensor(data=second_image_np).float() / 255
100+
second_image_tensor = (
101+
torch.tensor( # pyright: ignore[reportPrivateImportUsage]
102+
data=second_image_np,
103+
).float()
104+
/ 255
105+
)
96106
second_image_tensor = second_image_tensor.view(
97107
second_image_resized.size[1],
98108
second_image_resized.size[0],

src/mock_vws/target_raters.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ def _get_brisque_target_tracking_rating(*, image_content: bytes) -> int:
2828
image_file = io.BytesIO(initial_bytes=image_content)
2929
with Image.open(fp=image_file) as image:
3030
image_np = np.array(object=image, dtype=np.float32)
31-
image_tensor = torch.tensor(data=image_np).float() / 255
31+
image_tensor = (
32+
torch.tensor( # pyright: ignore[reportPrivateImportUsage]
33+
data=image_np,
34+
).float()
35+
/ 255
36+
)
3237
image_tensor = image_tensor.view(
3338
image.size[1],
3439
image.size[0],

0 commit comments

Comments
 (0)