diff options
author | Mario Corchero <mariocj89@gmail.com> | 2021-07-16 11:23:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-16 11:23:50 +0200 |
commit | 28da62dc5918da3c6cf37ac19b3ddf0585a1803a (patch) | |
tree | 10cb2f592e0f424d6c581b3b67e5afc7b11e6fde | |
parent | 6b035517571e63b6a63a493740c5506ec1e5da44 (diff) | |
parent | e78c3c76d2f0aaf6180c786605857a6ebc0cd3f3 (diff) | |
download | dateutil-git-28da62dc5918da3c6cf37ac19b3ddf0585a1803a.tar.gz |
Merge pull request #1007 from orsonadams/lazyload-py37
Lazyload submodules python37+
-rw-r--r-- | changelog.d/1007.feature.rst | 6 | ||||
-rw-r--r-- | dateutil/__init__.py | 16 | ||||
-rw-r--r-- | dateutil/test/test_imports.py | 64 |
3 files changed, 86 insertions, 0 deletions
diff --git a/changelog.d/1007.feature.rst b/changelog.d/1007.feature.rst new file mode 100644 index 0000000..33a7f55 --- /dev/null +++ b/changelog.d/1007.feature.rst @@ -0,0 +1,6 @@ +Made all ``dateutil`` submodules lazily imported using `PEP 562 +<https://www.python.org/dev/peps/pep-0562/>`_. On Python 3.7+, things like +``import dateutil; dateutil.tz.gettz("America/New_York")`` will now work +without explicitly importing ``dateutil.tz``, with the import occurring behind +the scenes on first use. The old behavior remains on Python 3.6 and earlier. +Fixed by Orson Adams. (gh issue #771, gh pr #1007) diff --git a/dateutil/__init__.py b/dateutil/__init__.py index 0defb82..a2c19c0 100644 --- a/dateutil/__init__.py +++ b/dateutil/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import sys + try: from ._version import version as __version__ except ImportError: @@ -6,3 +8,17 @@ except ImportError: __all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', 'utils', 'zoneinfo'] + +def __getattr__(name): + import importlib + + if name in __all__: + return importlib.import_module("." + name, __name__) + raise AttributeError( + "module {!r} has not attribute {!r}".format(__name__, name) + ) + + +def __dir__(): + # __dir__ should include all the lazy-importable modules as well. + return [x for x in globals() if x not in sys.modules] + __all__ diff --git a/dateutil/test/test_imports.py b/dateutil/test/test_imports.py index 60b8600..7d0749e 100644 --- a/dateutil/test/test_imports.py +++ b/dateutil/test/test_imports.py @@ -1,5 +1,69 @@ import sys +import unittest import pytest +import six + +MODULE_TYPE = type(sys) + + +# Tests live in datetutil/test which cause a RuntimeWarning for Python2 builds. +# But since we expect lazy imports tests to fail for Python < 3.7 we'll ignore those +# warnings with this filter. + +if six.PY2: + filter_import_warning = pytest.mark.filterwarnings("ignore::RuntimeWarning") +else: + + def filter_import_warning(f): + return f + + +@pytest.fixture(scope="function") +def clean_import(): + """Create a somewhat clean import base for lazy import tests""" + du_modules = { + mod_name: mod + for mod_name, mod in sys.modules.items() + if mod_name.startswith("dateutil") + } + + other_modules = { + mod_name for mod_name in sys.modules if mod_name not in du_modules + } + + for mod_name in du_modules: + del sys.modules[mod_name] + + yield + + # Delete anything that wasn't in the origin sys.modules list + for mod_name in list(sys.modules): + if mod_name not in other_modules: + del sys.modules[mod_name] + + # Restore original modules + for mod_name, mod in du_modules.items(): + sys.modules[mod_name] = mod + + +@filter_import_warning +@pytest.mark.parametrize( + "module", + ["easter", "parser", "relativedelta", "rrule", "tz", "utils", "zoneinfo"], +) +def test_lazy_import(clean_import, module): + """Test that dateutil.[submodule] works for py version > 3.7""" + + import dateutil, importlib + + if sys.version_info < (3, 7): + pytest.xfail("Lazy loading does not work for Python < 3.7") + + mod_obj = getattr(dateutil, module, None) + assert isinstance(mod_obj, MODULE_TYPE) + + mod_imported = importlib.import_module("dateutil.%s" % module) + assert mod_obj is mod_imported HOST_IS_WINDOWS = sys.platform.startswith('win') |