Defer asyncio and typing_extensions imports (lazy package init)#46
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".
35a86ff to
5ffc03e
Compare
Defer all submodule/function imports via PEP 562 (`__getattr__`/`__dir__`) so `import python_utils` no longer eagerly imports every submodule, and move `asyncio` (and `python_utils.aio`) imports inside `python_utils.time`'s async helpers. Consumers that only need the synchronous utilities no longer pay the cost of importing `asyncio`. - __init__.py: PEP 562 lazy loading; `__version__` kept eager (cheap metadata). Public `__all__` is unchanged and `dir()` still lists the lazy `containers`/`exceptions` submodules so `import_global()` keeps working. - time.py: `aio_timeout_generator(iterable=...)` now defaults to `None` and resolves to `aio.acount` lazily (the default cannot reference `aio.acount` without importing asyncio at module load); `asyncio` is imported inside the two async helpers. - tests: regression tests proving a bare `import python_utils` and `import python_utils.time` pull in neither `asyncio` nor `typing_extensions`. Public API verified against the 4.0.0 base via an export/signature diff: the only differences are the two inherent consequences of deferring asyncio -- `aio_timeout_generator`'s default iterable is `None` instead of `aio.acount`, and `python_utils.time` no longer re-exposes the `aio`/`asyncio` modules.
c76b511 to
9d9f76d
Compare
Builds on the lazy package init: removes typing_extensions from the submodule import path and defers __version__. - Add stdlib-only python_utils/_aliases.py holding the lightweight type aliases; python_utils.types re-exports them (explicit `X as X` idiom) and keeps its eager typing_extensions facade + runtime overrides unchanged. - Rewire time/converters/formatters/generators/import_ off python_utils.types onto _aliases, so importing them no longer pulls typing_extensions. - Move logger's only typing_extensions use (Logged.__new__ -> Self) into TYPE_CHECKING (Self is a new addition; runtime introspection of __new__'s return type is not a compat obligation). - Make python_utils.__version__ lazy via the PEP 562 __getattr__ so bare `import python_utils` no longer calls importlib.metadata.version() (~89 -> ~8 modules added to sys.modules). - Add a deterministic import-footprint regression gate + get_type_hints smoke. Public API verified byte-identical to develop via an export/signature manifest diff, except: (a) aio_timeout_generator's default iterable (from the lazy asyncio change) and (b) converters' raw __annotations__ strings now name the bare aliases (get_type_hints resolves identically). `from __future__ import annotations` is limited to where required (logger, containers) so inspect.signature keeps returning evaluated type objects elsewhere. The undocumented python_utils.<submodule>.types re-export attribute is removed.
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.