Skip to content

Add lazy imports in header and PEP 810 lazy import syntax#898

Merged
evhub merged 4 commits into
developfrom
claude/fix-coconut-886-V26PW
Feb 6, 2026
Merged

Add lazy imports in header and PEP 810 lazy import syntax#898
evhub merged 4 commits into
developfrom
claude/fix-coconut-886-V26PW

Conversation

@evhub
Copy link
Copy Markdown
Owner

@evhub evhub commented Feb 3, 2026

Summary

Lazy Header Imports (#886)

Third-party imports (async_generator, tstr, numpy, trollius, backports.functools_lru_cache) are now lazy-loaded using _coconut_lazy_module, a function that creates a per-module class with a metaclass handling attribute forwarding.

PEP 810 Lazy Import Syntax (#890)

New syntax:

  • lazy import json — defers loading until first attribute access
  • lazy from json import dumps — defers loading until first use

Restrictions:

  • lazy from __future__ import ... → SyntaxError
  • lazy from x import * → SyntaxError

Test plan

  • make test passes
  • Manual testing of lazy import and lazy from ... import syntax
  • Verified error messages for disallowed lazy imports

Closes #886
Closes #890

https://claude.ai/code/session_01KBLQLYTdfjLPaR2UJhgnkq

@evhub evhub changed the base branch from master to develop February 3, 2026 06:05
@evhub evhub added the feature label Feb 3, 2026
@evhub evhub added this to the v3.2.1 milestone Feb 3, 2026
Copy link
Copy Markdown
Owner Author

@evhub evhub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also need to add tests, including for all combinations of lazy imports with from and as as well as imports changed across Python versions that should be remapped.

def __call__(cls, *args, **kwargs):
return load()(*args, **kwargs)
def __iter__(cls):
return _coconut.iter(load())
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not need to manually set any dunders if the metaclass approach is working properly.

)
import_stmt = Forward()
import_stmt_ref = from_import | basic_import
import_stmt_ref = Optional(keyword("lazy"), default="") + (from_import | basic_import)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using default, let's just detect the number of tokens.

Comment thread coconut/compiler/compiler.py Outdated
self.name_info[imp_name]["imported"].add(loc)
if lazy:
return self.lazy_import(loc, imports, imp_from=imp_from)
return self.universal_import(loc, imports, imp_from=imp_from)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make the flow clearer, put this in an else.

Comment thread coconut/compiler/compiler.py Outdated
)
return "\n".join(stmts)

def lazy_import(self, loc, imports, imp_from=None):
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

universal_import does a lot more that this doesn't currently handle, like py2/py3 mapping. Let's fold this into that function instead of using it as a separate one (we should be replacing single_import instead of universal_import).

Implements `lazy import x` and `lazy from x import y` syntax per PEP 810.
Lazy imports defer module loading until first attribute access.

- Add `lazy` as optional prefix keyword in import grammar
- Add `lazy_import()` method to generate _coconut_lazy_module calls
- Add __call__ and __iter__ to lazy module metaclass for callable/iterable attrs
- Reject lazy imports for star imports and __future__

Closes #890

https://claude.ai/code/session_01KBLQLYTdfjLPaR2UJhgnkq
- Add comprehensive tests for lazy import syntax (PEP 810)
- Add _coconut_lazy_module to underscore_imports for package compilation
- Add dir builtin to _coconut class for lazy module __dir__
- Update stub files with _coconut_lazy_module and dir

Tests cover:
- lazy import x
- lazy import x as y
- lazy from x import y
- lazy from x import y as z
- lazy from x import a, b
- Remapped imports (queue -> Queue on py2)

https://claude.ai/code/session_01KBLQLYTdfjLPaR2UJhgnkq
@evhub evhub force-pushed the claude/fix-coconut-886-V26PW branch from a69a614 to efa7c3c Compare February 6, 2026 00:32
- Remove __call__ and __iter__ from _coconut_lazy_module metaclass
- Detect lazy keyword by token count instead of using default=""
- Put lazy/non-lazy import_handle return in if/else
- Fold lazy_import into single_import/universal_import instead of
  being a separate method; lazy from-imports now use immediate
  attribute access on the lazy module proxy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread coconut/compiler/compiler.py Outdated
imp, imp_as = imp_as, None

if imp_as is not None and "." in imp_as:
if lazy:
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't buy that this is the right intervention point; why don't we still need to handle fake_mods like below? I think you should be adding a lazy arg to import_stmt and handling it there instead.

Comment thread _coconut/__init__.pyi
zip_longest = _zip_longest

import importlib as _importlib
_coconut_lazy_module = _importlib.import_module
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this showing up in the diff? Isn't this already in develop?

Comment thread coconut/compiler/grammar.py Outdated
)
import_stmt = Forward()
import_stmt_ref = from_import | basic_import
import_stmt_ref = Optional(keyword("lazy")) + (from_import | basic_import)
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was better when we were using default="" here so that the first token was always either "lazy" or "" and we didn't have to do other weird stuff with the number of tokens. let's go back to that.

- Revert grammar to use default="" for Optional lazy keyword
- Simplify import_handle token parsing back to tokens[0] == "lazy"
- Move lazy import handling from single_import to import_stmt function
- single_import now just passes lazy through to import_stmt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@evhub evhub added the resolved label Feb 6, 2026
@evhub evhub merged commit b463934 into develop Feb 6, 2026
1 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add lazy import syntax Use lazy imports in the header

2 participants