Defer asyncio and typing_extensions imports (lazy package init)#46
Defer asyncio and typing_extensions imports (lazy package init)#46wolph wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces lazy loading to the python_utils package to avoid eagerly importing heavy dependencies like asyncio and typing_extensions when only synchronous utilities are needed. This is achieved by utilizing PEP 562 (__getattr__ and __dir__) in python_utils/__init__.py and python_utils/types.py, and by moving asyncio imports inside the relevant asynchronous functions in python_utils/time.py. Feedback is provided to improve a type cast in python_utils/time.py by using actual type objects instead of string literals and accurately typing aio.acount as a callable.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR updates python_utils to avoid eagerly importing heavier dependencies (notably asyncio and, on 3.11+, typing_extensions) by switching the package __init__ to PEP 562 lazy attribute loading and moving asyncio imports into async-only call paths.
Changes:
- Implement PEP 562 lazy exports in
python_utils/__init__.py(__getattr__,__dir__) soimport python_utilsdoesn’t import submodules immediately. - Defer
asyncio(andaio) imports inpython_utils/time.pyto async functions only; make theaio_timeout_generatordefault iterable resolved at runtime. - Version-gate / lazily resolve
typing_extensionsnames inpython_utils/types.pyon Python 3.11+.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
python_utils/__init__.py |
Replaces eager re-exports with lazy attribute-based loading via PEP 562. |
python_utils/time.py |
Moves asyncio/aio imports into async functions and resolves default iterable lazily. |
python_utils/types.py |
Avoids eager typing_extensions import on 3.11+ and adds __getattr__ fallback. |
python_utils/converters.py |
Minor docstring capitalization tweak for remap. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 952d55844b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
`import python_utils` now imports nothing eagerly: the package __init__ uses PEP 562 __getattr__ to load submodules and their exported names on first access (with a TYPE_CHECKING block so static typing is unchanged). This avoids pulling in asyncio for consumers that only need the synchronous utilities. time.py no longer imports asyncio/aio at module scope (moved into the two async generators; the `aio.acount` default is resolved lazily), so `from python_utils.time import format_time` stays asyncio-free. types.py imports typing_extensions eagerly only on Python < 3.11 (to preserve the backport overrides); on 3.11+ stdlib typing already provides the names used and any remaining typing_extensions-only name is served lazily via __getattr__. Net effect for a typical consumer (measured via python-progressbar): import drops ~43ms -> ~22ms, with asyncio and typing_extensions no longer loaded. Behaviour and public API are unchanged; full test suite passes.
…tests - time.py: cast aio.acount to Callable[[], AsyncIterable[_T]] (real type object instead of a string forward-ref) so static analysis/refactor works - types.py: revert typing_extensions deferral; restore runtime 'from typing_extensions import *' so backported runtime behaviour (e.g. TypeVar(default=...)) is preserved on Python 3.9-3.12. Bare 'import python_utils' still avoids typing_extensions (types loads lazily) - __init__.py: __dir__ now includes lazy submodules (containers, exceptions) so dir(python_utils) and import_global() see them again - test_lazy_imports.py: drop the removed types.__getattr__ test; add a clean-subprocess test proving bare import pulls in neither asyncio nor typing_extensions, plus __getattr__ caching and __dir__ coverage
Alias the internal 'importlib'/'typing' imports to '_importlib'/'_typing' so they no longer leak as public attributes of 'python_utils' (they appeared in dir() and as python_utils.importlib / python_utils.typing under the lazy __init__, which develop did not expose). Add an explicit '# pragma: no cover' to the TYPE_CHECKING block since coverage's auto-exclusion only matches the unaliased 'typing.TYPE_CHECKING' spelling. Verified via a full public-API manifest diff against develop: the only remaining differences are the two inherent consequences of deferring asyncio (aio_timeout_generator's default iterable is now None instead of aio.acount, and python_utils.time no longer re-exposes the 'aio'/'asyncio' modules).
import python_utilsnow imports nothing eagerly — the package__init__uses PEP 562__getattr__to load submodules and their exported names on first access (with aTYPE_CHECKINGblock so static typing is unchanged). This avoids pulling in asyncio for consumers that only need the synchronous utilities.time.py:asyncio/aiono longer imported at module scope (moved into the two async generators; theaio.acountdefault is resolved lazily), sofrom python_utils.time import format_timestays asyncio-free.types.py:typing_extensionsis imported eagerly only on Python < 3.11 (to preserve backport overrides); on 3.11+ stdlibtypingalready provides the names used, and any remaining typing_extensions-only name is served lazily via__getattr__.Net effect for a typical consumer (measured via python-progressbar): cold import ~43ms → ~22ms, with asyncio and typing_extensions no longer loaded. Behaviour and public API unchanged; full test suite passes.
Note: the
docs_and_lintjob currently also flags pre-existing mypy/pyright issues in untouched files (logger.pywraps_classmethodtyping,terminal.pystale# type: ignorecodes,loguru/setuptoolsstubs) — these are equally red ondevelop(tool/stub drift) and are not introduced by this PR.