File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1111
1212from astropy .utils .system_info import system_info
1313
14+ __all__ = [ # noqa: RUF100, RUF022
15+ "__version__" ,
16+ "__bibtex__" ,
17+ # Subpackages (mostly lazy-loaded)
18+ "config" ,
19+ "constants" ,
20+ "convolution" ,
21+ "coordinates" ,
22+ "cosmology" ,
23+ "io" ,
24+ "modeling" ,
25+ "nddata" ,
26+ "samp" ,
27+ "stats" ,
28+ "table" ,
29+ "tests" ,
30+ "time" ,
31+ "timeseries" ,
32+ "uncertainty" ,
33+ "units" ,
34+ "utils" ,
35+ "visualization" ,
36+ "wcs" ,
37+ # Functions
38+ "test" ,
39+ "log" ,
40+ "find_api_page" ,
41+ "online_help" ,
42+ "online_docs_root" ,
43+ "conf" ,
44+ "physical_constants" ,
45+ "astronomical_constants" ,
46+ "system_info" ,
47+ ]
48+
49+
50+ def __getattr__ (attr ):
51+ if attr in __all__ :
52+ from importlib import import_module
53+
54+ return import_module ("astropy." + attr )
55+
56+ raise AttributeError (f"module 'astropy' has no attribute { attr !r} " )
57+
58+
59+ def __dir__ ():
60+ return sorted (set (globals ()).union (__all__ ))
61+
62+
1463from .version import version as __version__
1564
1665# The location of the online documentation for astropy
@@ -198,30 +247,14 @@ def online_help(query):
198247 webbrowser .open (url )
199248
200249
201- __dir_inc__ = [
202- "__version__" ,
203- "__githash__" ,
204- "__bibtex__" ,
205- "test" ,
206- "log" ,
207- "find_api_page" ,
208- "online_help" ,
209- "online_docs_root" ,
210- "conf" ,
211- "physical_constants" ,
212- "astronomical_constants" ,
213- "system_info" ,
214- ]
215-
216-
217250from types import ModuleType as __module_type__
218251
219- # Clean up top-level namespace--delete everything that isn't in __dir_inc__
252+ # Clean up top-level namespace--delete everything that isn't in __all__
220253# or is a magic attribute, and that isn't a submodule of this package
221254for varname in dir ():
222255 if not (
223256 (varname .startswith ("__" ) and varname .endswith ("__" ))
224- or varname in __dir_inc__
257+ or varname in __all__
225258 or (
226259 varname [0 ] != "_"
227260 and isinstance (locals ()[varname ], __module_type__ )
Original file line number Diff line number Diff line change 11# Licensed under a 3-clause BSD style license - see LICENSE.rst
22
33import pkgutil
4+ import subprocess
5+ import sys
6+ from pathlib import Path
7+ from textwrap import dedent
8+ from types import ModuleType
9+
10+ import pytest
11+
12+ import astropy
413
514
615def test_imports ():
@@ -32,3 +41,38 @@ def test_toplevel_namespace():
3241 assert "log" in d
3342 assert "test" in d
3443 assert "sys" not in d
44+
45+
46+ def test_toplevel_lazy_imports ():
47+ # Check that subpackages are loaded on demand.
48+ cmd = dedent ("""
49+ import astropy, sys
50+ assert 'astropy.units' not in sys.modules
51+ astropy.units
52+ assert 'astropy.units' in sys.modules
53+ """ )
54+ cp = subprocess .check_call ([sys .executable , "-c" , cmd ])
55+ assert cp == 0
56+
57+
58+ def test_toplevel_attribute_error ():
59+ # Ensure that our __getattr__ does not leak an import error or so.
60+ with pytest .raises (AttributeError , match = "module 'astropy' has no" ):
61+ astropy .nonsense
62+
63+
64+ def test_completeness_toplevel__all__ ():
65+ # Implicitly check that all items in __all__ exist.
66+ all_items = {getattr (astropy , attr ) for attr in astropy .__all__ }
67+ # Verify that the list of modules in __all__ is complete.
68+ module_names = {
69+ item .__name__ .partition ("." )[2 ]
70+ for item in all_items
71+ if isinstance (item , ModuleType )
72+ }
73+ module_dirs = {
74+ f .name
75+ for f in Path (astropy .__file__ ).parent .glob ("[a-z]*" )
76+ if f .is_dir () and f .name != "extern"
77+ }
78+ assert module_names == module_dirs
Original file line number Diff line number Diff line change @@ -338,9 +338,9 @@ def find_mod_objs(modname, onlylocals=False):
338338 mod = import_module (modname )
339339
340340 if hasattr (mod , "__all__" ):
341- pkgitems = [(k , mod . __dict__ [ k ] ) for k in mod .__all__ ]
341+ pkgitems = [(k , getattr ( mod , k ) ) for k in mod .__all__ ]
342342 else :
343- pkgitems = [(k , mod . __dict__ [ k ] ) for k in dir (mod ) if k [0 ] != "_" ]
343+ pkgitems = [(k , getattr ( mod , k ) ) for k in dir (mod ) if k [0 ] != "_" ]
344344
345345 # filter out modules and pull the names and objs out
346346 ismodule = inspect .ismodule
Original file line number Diff line number Diff line change 1+ After ``import astropy ``, ``dir(astropy) `` will now list all subpackages,
2+ including those that have not yet been loaded. This also means tab
3+ completion will work as expected (e.g., ``from astropy.coo<TAB> `` will
4+ expand to ``from astropy.coordinates ``).
Original file line number Diff line number Diff line change 1- On representations the method `get_name ` has been deprecated in favor of the class-level
2- attribute `name `. The method will be removed in a future release.
1+ On representations the method `` get_name ` ` has been deprecated in favor of the class-level
2+ attribute `` name ` `. The method will be removed in a future release.
Original file line number Diff line number Diff line change @@ -74,6 +74,7 @@ py:class None. Remove all items from D.
7474py:class a set-like object providing a view on D's items
7575py:class a set-like object providing a view on D's keys
7676py:class None. Update D from dict/iterable E and F.
77+ py:class None. Update D from mapping/iterable E and F.
7778py:class an object providing a view on D's values
7879py:class a shallow copy of D
7980
You can’t perform that action at this time.
0 commit comments