Skip to content

Commit 45c128b

Browse files
committed
Finish reorganizing; fix assertion for duplicated messages
To support the changes, this adds typing-extensions as a test dependency, since we are now importing from it in a test module. But this should probably be required and used conditionally based on whether the Python version has assert_type in its typing module.
1 parent 9d58e6d commit 45c128b

File tree

2 files changed

+50
-49
lines changed

2 files changed

+50
-49
lines changed

test-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pytest-cov
88
pytest-instafail
99
pytest-mock
1010
pytest-sugar
11+
typing-extensions

test/deprecation/test_attributes.py

+49-49
Original file line numberDiff line numberDiff line change
@@ -4,61 +4,72 @@
44
checks static typing of the code under test. (Running pytest checks dynamic behavior.)
55
"""
66

7-
import importlib
7+
from itertools import groupby
88
from typing import Type
99

1010
import pytest
11+
from typing_extensions import assert_type
1112

1213
import git
1314

1415

15-
def test_cannot_get_undefined() -> None:
16+
def test_cannot_access_undefined() -> None:
17+
"""Accessing a bogus attribute in git remains both a dynamic and static error."""
1618
with pytest.raises(AttributeError):
1719
git.foo # type: ignore[attr-defined]
1820

1921

2022
def test_cannot_import_undefined() -> None:
23+
"""Importing a bogus attribute from git remains both a dynamic and static error."""
2124
with pytest.raises(ImportError):
2225
from git import foo # type: ignore[attr-defined] # noqa: F401
2326

2427

25-
def test_util_alias_members_resolve() -> None:
26-
"""git.index.util members can be accessed via git.util, and mypy recognizes it."""
27-
gu_tfs = git.util.TemporaryFileSwap
28-
from git.index.util import TemporaryFileSwap
28+
def test_util_alias_access() -> None:
29+
"""Accessing util in git works, warns, and mypy verifies it and its attributes."""
30+
# The attribute access should succeed.
31+
with pytest.deprecated_call() as ctx:
32+
util = git.util
2933

30-
def accepts_tfs_type(t: Type[TemporaryFileSwap]) -> None:
31-
pass
34+
# There should be exactly one warning and it should have our util-specific message.
35+
(message,) = [str(entry.message) for entry in ctx]
36+
assert "git.util" in message
37+
assert "git.index.util" in message
38+
assert "should not be relied on" in message
3239

33-
def rejects_tfs_type(t: Type[git.Git]) -> None:
34-
pass
40+
# We check access through the util alias to the TemporaryFileSwap member, since it
41+
# is slightly simpler to validate and reason about than the other public members,
42+
# which are functions (specifically, higher-order functions for use as decorators).
43+
from git.index.util import TemporaryFileSwap
3544

36-
# TODO: When typing_extensions is made a test dependency, use assert_type for this.
37-
accepts_tfs_type(gu_tfs)
38-
rejects_tfs_type(gu_tfs) # type: ignore[arg-type]
45+
assert_type(util.TemporaryFileSwap, Type[TemporaryFileSwap])
3946

40-
assert gu_tfs is TemporaryFileSwap
47+
# This comes after the static assertion, just in case it would affect the inference.
48+
assert util.TemporaryFileSwap is TemporaryFileSwap
4149

4250

43-
def test_util_alias_access_warns() -> None:
51+
def test_util_alias_import() -> None:
52+
"""Importing util from git works, warns, and mypy verifies it and its attributes."""
53+
# The import should succeed.
4454
with pytest.deprecated_call() as ctx:
45-
git.util
55+
from git import util
4656

47-
assert len(ctx) == 1
48-
message = str(ctx[0].message)
57+
# There may be multiple warnings. In CPython there will be currently always be
58+
# exactly two, possibly due to the equivalent of calling hasattr to do a pre-check
59+
# prior to retrieving the attribute for actual use. However, all warnings should
60+
# have the same message, and it should be our util-specific message.
61+
(message,) = {str(entry.message) for entry in ctx}
4962
assert "git.util" in message
5063
assert "git.index.util" in message
5164
assert "should not be relied on" in message
5265

66+
# As above, we check access through the util alias to the TemporaryFileSwap member.
67+
from git.index.util import TemporaryFileSwap
5368

54-
def test_util_alias_import_warns() -> None:
55-
with pytest.deprecated_call() as ctx:
56-
from git import util # noqa: F401
69+
assert_type(util.TemporaryFileSwap, Type[TemporaryFileSwap])
5770

58-
message = str(ctx[0].message)
59-
assert "git.util" in message
60-
assert "git.index.util" in message
61-
assert "should not be relied on" in message
71+
# This comes after the static assertion, just in case it would affect the inference.
72+
assert util.TemporaryFileSwap is TemporaryFileSwap
6273

6374

6475
# Split out util and have all its tests be separate, above.
@@ -71,12 +82,11 @@ def test_util_alias_import_warns() -> None:
7182
git.index.base,
7283
git.index.fun,
7384
git.index.typ,
74-
git.index.util,
7585
)
7686

7787

78-
def test_private_module_alias_access_on_git_module() -> None:
79-
"""Private alias access works, warns, and except for util is a mypy error."""
88+
def test_private_module_alias_access() -> None:
89+
"""Non-util private alias access works, warns, but is a deliberate mypy error."""
8090
with pytest.deprecated_call() as ctx:
8191
assert (
8292
git.head, # type: ignore[attr-defined]
@@ -87,21 +97,16 @@ def test_private_module_alias_access_on_git_module() -> None:
8797
git.base, # type: ignore[attr-defined]
8898
git.fun, # type: ignore[attr-defined]
8999
git.typ, # type: ignore[attr-defined]
90-
git.util,
91100
) == _MODULE_ALIAS_TARGETS
92101

102+
# Each should have warned exactly once, and note what to use instead.
93103
messages = [str(w.message) for w in ctx]
94-
for target, message in zip(_MODULE_ALIAS_TARGETS[:-1], messages[:-1], strict=True):
104+
for target, message in zip(_MODULE_ALIAS_TARGETS, messages, strict=True):
95105
assert message.endswith(f"Use {target.__name__} instead.")
96106

97-
util_message = messages[-1]
98-
assert "git.util" in util_message
99-
assert "git.index.util" in util_message
100-
assert "should not be relied on" in util_message
101107

102-
103-
def test_private_module_alias_import_from_git_module() -> None:
104-
"""Private alias import works, warns, and except for util is a mypy error."""
108+
def test_private_module_alias_import() -> None:
109+
"""Non-util private alias access works, warns, but is a deliberate mypy error."""
105110
with pytest.deprecated_call() as ctx:
106111
from git import head # type: ignore[attr-defined]
107112
from git import log # type: ignore[attr-defined]
@@ -111,7 +116,6 @@ def test_private_module_alias_import_from_git_module() -> None:
111116
from git import base # type: ignore[attr-defined]
112117
from git import fun # type: ignore[attr-defined]
113118
from git import typ # type: ignore[attr-defined]
114-
from git import util
115119

116120
assert (
117121
head,
@@ -122,17 +126,13 @@ def test_private_module_alias_import_from_git_module() -> None:
122126
base,
123127
fun,
124128
typ,
125-
util,
126129
) == _MODULE_ALIAS_TARGETS
127130

128-
# FIXME: This fails because, with imports, multiple consecutive accesses may occur.
129-
# In practice, with CPython, it is always exactly two accesses, the first from the
130-
# equivalent of a hasattr, and the second to fetch the attribute intentionally.
131-
messages = [str(w.message) for w in ctx]
132-
for target, message in zip(_MODULE_ALIAS_TARGETS[:-1], messages[:-1], strict=True):
131+
# Each import may warn multiple times. In CPython there will be currently always be
132+
# exactly two warnings per import, possibly due to the equivalent of calling hasattr
133+
# to do a pre-check prior to retrieving the attribute for actual use. However, for
134+
# each import, all messages should be the same and should note what to use instead.
135+
messages_with_duplicates = [str(w.message) for w in ctx]
136+
messages = [message for message, _ in groupby(messages_with_duplicates)]
137+
for target, message in zip(_MODULE_ALIAS_TARGETS, messages, strict=True):
133138
assert message.endswith(f"Use {target.__name__} instead.")
134-
135-
util_message = messages[-1]
136-
assert "git.util" in util_message
137-
assert "git.index.util" in util_message
138-
assert "should not be relied on" in util_message

0 commit comments

Comments
 (0)