33from __future__ import annotations
44
55from collections import defaultdict
6+ from collections .abc import Iterable
67from fnmatch import fnmatch
78from pathlib import Path
89from typing import TYPE_CHECKING
1819 from types import ModuleType
1920
2021
21- def _load_module ( module : ModuleType ) -> griffe .GriffeLoader :
22+ def _load_modules ( * modules : ModuleType ) -> griffe .GriffeLoader :
2223 loader = griffe .GriffeLoader (
2324 extensions = griffe .load_extensions (
2425 "griffe_inherited_docstrings" ,
2526 "unpack_typeddict" ,
2627 ),
2728 )
28- loader .load (module .__name__ )
29+ for module in modules :
30+ loader .load (module .__name__ )
2931 loader .resolve_aliases ()
3032 return loader
3133
3234
3335def _get_internal_api (module : ModuleType , loader : griffe .GriffeLoader | None = None ) -> griffe .Module :
3436 if loader is None :
35- loader = _load_module (module )
37+ loader = _load_modules (module )
3638 return loader .modules_collection [module .__name__ + "._internal" ]
3739
3840
41+ def _get_reexported_names (module : ModuleType ) -> Iterable [str ]:
42+ return getattr (module , "_REEXPORTED_EXTERNAL_API" , ())
43+
44+
3945def _get_public_api (module : ModuleType , loader : griffe .GriffeLoader | None = None ) -> griffe .Module :
4046 if loader is None :
41- loader = _load_module (module )
47+ loader = _load_modules (module )
4248 return loader .modules_collection [module .__name__ ]
4349
4450
@@ -143,7 +149,7 @@ def _public_path(obj: griffe.Object | griffe.Alias) -> bool:
143149
144150 public_api = _get_public_api (tested_module )
145151 multiple_locations = {}
146- for obj_name in set (tested_module .__all__ ) - getattr ( tested_module , "_SINGLE_LOCATIONS_IGNORE" , set ( )):
152+ for obj_name in set (tested_module .__all__ ). difference ( _get_reexported_names ( tested_module )):
147153 obj = public_api [obj_name ]
148154 if obj .aliases and (
149155 public_aliases := [path for path , alias in obj .aliases .items () if path != obj .path and _public_path (alias )]
@@ -154,10 +160,15 @@ def _public_path(obj: griffe.Object | griffe.Alias) -> bool:
154160 )
155161
156162
157- def test_api_matches_inventory (inventory : Inventory , public_objects : list [griffe .Object | griffe .Alias ]) -> None :
163+ @pytest .mark .parametrize ("tested_module" , [griffe , griffecli ])
164+ def test_api_matches_inventory (inventory : Inventory , tested_module : ModuleType ) -> None :
158165 """All public objects are added to the inventory."""
159166 ignore_names = {"__getattr__" , "__init__" , "__repr__" , "__str__" , "__post_init__" }
167+ ignore_names .update (_get_reexported_names (tested_module ))
160168 ignore_paths = {"griffe.DataclassesExtension.*" , "griffe.UnpackTypedDictExtension.*" }
169+ loader = _load_modules (tested_module )
170+ public_api = _get_public_api (tested_module , loader = loader )
171+ public_objects = _get_public_objects (public_api )
161172 not_in_inventory = [
162173 f"{ obj .relative_filepath } :{ obj .lineno } : { obj .path } "
163174 for obj in public_objects
@@ -171,20 +182,27 @@ def test_api_matches_inventory(inventory: Inventory, public_objects: list[griffe
171182 assert not not_in_inventory , msg .format (paths = "\n " .join (sorted (not_in_inventory )))
172183
173184
174- @pytest .mark .parametrize ("tested_module" , [griffe , griffecli ])
175- def test_inventory_matches_api (inventory : Inventory , tested_module : ModuleType ) -> None :
185+ def test_inventory_matches_api (inventory : Inventory ) -> None :
176186 """The inventory doesn't contain any additional Python object."""
177- loader = _load_module (tested_module )
178- public_api = _get_public_api (tested_module , loader = loader )
179- public_objects = _get_public_objects (public_api )
187+ tested_modules = (griffe , griffecli )
188+ loader = _load_modules (* tested_modules )
180189 not_in_api = []
181- public_api_paths = {obj .path for obj in public_objects }
182- public_api_paths .add (tested_module .__name__ )
190+ public_objects = []
191+ public_api_paths = set ()
192+
193+ for tested_module in tested_modules :
194+ public_api = _get_public_api (tested_module , loader = loader )
195+ module_public_objects = _get_public_objects (public_api )
196+ public_api_paths .add (tested_module .__name__ )
197+ public_api_paths .update ({obj .path for obj in module_public_objects })
198+ public_objects .extend (module_public_objects )
199+
183200 for item in inventory .values ():
184201 if item .domain == "py" and "(" not in item .name :
185202 obj = loader .modules_collection [item .name ]
186203 if obj .path not in public_api_paths and not any (path in public_api_paths for path in obj .aliases ):
187204 not_in_api .append (item .name )
205+
188206 msg = "Inventory objects not in public API (try running `make run mkdocs build`):\n {paths}"
189207 assert not not_in_api , msg .format (paths = "\n " .join (sorted (not_in_api )))
190208
0 commit comments