1
- """Tests for dynamic and static attribute errors."""
1
+ """Tests for dynamic and static attribute errors in GitPython's top-level git module.
2
+
3
+ Provided mypy has ``warn_unused_ignores = true`` set, running mypy on these test cases
4
+ checks static typing of the code under test. (Running pytest checks dynamic behavior.)
5
+ """
2
6
3
7
import importlib
4
8
from typing import Type
@@ -18,17 +22,6 @@ def test_cannot_import_undefined() -> None:
18
22
from git import foo # type: ignore[attr-defined] # noqa: F401
19
23
20
24
21
- def test_util_alias_access_resolves () -> None :
22
- """These resolve for now, though they're private and we do not guarantee this."""
23
- assert git .util is git .index .util
24
-
25
-
26
- def test_util_alias_import_resolves () -> None :
27
- from git import util
28
-
29
- assert util is git .index .util
30
-
31
-
32
25
def test_util_alias_members_resolve () -> None :
33
26
"""git.index.util members can be accessed via git.util, and mypy recognizes it."""
34
27
gu_tfs = git .util .TemporaryFileSwap
@@ -68,59 +61,78 @@ def test_util_alias_import_warns() -> None:
68
61
assert "should not be relied on" in message
69
62
70
63
71
- def test_private_module_aliases_exist_dynamically () -> None :
72
- """These exist at runtime (for now) but mypy treats them as absent (intentionally).
73
-
74
- This code verifies the effect of static type checking when analyzed by mypy, if mypy
75
- is configured with ``warn_unused_ignores = true``.
76
-
77
- More detailed dynamic behavior is examined in the subsequent test cases.
78
- """
79
- git .head # type: ignore[attr-defined]
80
- git .log # type: ignore[attr-defined]
81
- git .reference # type: ignore[attr-defined]
82
- git .symbolic # type: ignore[attr-defined]
83
- git .tag # type: ignore[attr-defined]
84
- git .base # type: ignore[attr-defined]
85
- git .fun # type: ignore[attr-defined]
86
- git .typ # type: ignore[attr-defined]
87
-
88
-
89
- @pytest .mark .parametrize (
90
- "name, fullname" ,
91
- [
92
- ("head" , "git.refs.head" ),
93
- ("log" , "git.refs.log" ),
94
- ("reference" , "git.refs.reference" ),
95
- ("symbolic" , "git.refs.symbolic" ),
96
- ("tag" , "git.refs.tag" ),
97
- ("base" , "git.index.base" ),
98
- ("fun" , "git.index.fun" ),
99
- ("typ" , "git.index.typ" ),
100
- ],
64
+ # Split out util and have all its tests be separate, above.
65
+ _MODULE_ALIAS_TARGETS = (
66
+ git .refs .head ,
67
+ git .refs .log ,
68
+ git .refs .reference ,
69
+ git .refs .symbolic ,
70
+ git .refs .tag ,
71
+ git .index .base ,
72
+ git .index .fun ,
73
+ git .index .typ ,
74
+ git .index .util ,
101
75
)
102
- class TestPrivateModuleAliases :
103
- """Tests of the private module aliases' shared specific runtime behaviors."""
104
76
105
- def test_private_module_alias_access_resolves (self , name : str , fullname : str ) -> None :
106
- """These resolve for now, though they're private and we do not guarantee this."""
107
- assert getattr (git , name ) is importlib .import_module (fullname )
108
77
109
- def test_private_module_alias_import_resolves (self , name : str , fullname : str ) -> None :
110
- exec (f"from git import { name } " )
111
- assert locals ()[name ] is importlib .import_module (fullname )
112
-
113
- def test_private_module_alias_access_warns (self , name : str , fullname : str ) -> None :
114
- with pytest .deprecated_call () as ctx :
115
- getattr (git , name )
116
-
117
- assert len (ctx ) == 1
118
- message = str (ctx [0 ].message )
119
- assert message .endswith (f"Use { fullname } instead." )
120
-
121
- def test_private_module_alias_import_warns (self , name : str , fullname : str ) -> None :
122
- with pytest .deprecated_call () as ctx :
123
- exec (f"from git import { name } " )
124
-
125
- message = str (ctx [0 ].message )
126
- assert message .endswith (f"Use { fullname } instead." )
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."""
80
+ with pytest .deprecated_call () as ctx :
81
+ assert (
82
+ git .head , # type: ignore[attr-defined]
83
+ git .log , # type: ignore[attr-defined]
84
+ git .reference , # type: ignore[attr-defined]
85
+ git .symbolic , # type: ignore[attr-defined]
86
+ git .tag , # type: ignore[attr-defined]
87
+ git .base , # type: ignore[attr-defined]
88
+ git .fun , # type: ignore[attr-defined]
89
+ git .typ , # type: ignore[attr-defined]
90
+ git .util ,
91
+ ) == _MODULE_ALIAS_TARGETS
92
+
93
+ messages = [str (w .message ) for w in ctx ]
94
+ for target , message in zip (_MODULE_ALIAS_TARGETS [:- 1 ], messages [:- 1 ], strict = True ):
95
+ assert message .endswith (f"Use { target .__name__ } instead." )
96
+
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
101
+
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."""
105
+ with pytest .deprecated_call () as ctx :
106
+ from git import head # type: ignore[attr-defined]
107
+ from git import log # type: ignore[attr-defined]
108
+ from git import reference # type: ignore[attr-defined]
109
+ from git import symbolic # type: ignore[attr-defined]
110
+ from git import tag # type: ignore[attr-defined]
111
+ from git import base # type: ignore[attr-defined]
112
+ from git import fun # type: ignore[attr-defined]
113
+ from git import typ # type: ignore[attr-defined]
114
+ from git import util
115
+
116
+ assert (
117
+ head ,
118
+ log ,
119
+ reference ,
120
+ symbolic ,
121
+ tag ,
122
+ base ,
123
+ fun ,
124
+ typ ,
125
+ util ,
126
+ ) == _MODULE_ALIAS_TARGETS
127
+
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 ):
133
+ 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