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+ """
26
37import importlib
48from typing import Type
@@ -18,17 +22,6 @@ def test_cannot_import_undefined() -> None:
1822 from git import foo # type: ignore[attr-defined] # noqa: F401
1923
2024
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-
3225def test_util_alias_members_resolve () -> None :
3326 """git.index.util members can be accessed via git.util, and mypy recognizes it."""
3427 gu_tfs = git .util .TemporaryFileSwap
@@ -68,59 +61,78 @@ def test_util_alias_import_warns() -> None:
6861 assert "should not be relied on" in message
6962
7063
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 ,
10175)
102- class TestPrivateModuleAliases :
103- """Tests of the private module aliases' shared specific runtime behaviors."""
10476
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 )
10877
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