From adc2fd9a69ad18680709d6751bd7fac0b36afd4d Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sun, 11 Jun 2023 15:43:51 -0400 Subject: [PATCH] Delay `astroid_bootstrapping()` until instantiating `AstroidBuilder` --- ChangeLog | 7 ++++++- astroid/brain/brain_builtin_inference.py | 14 ++++++++------ astroid/brain/brain_nose.py | 19 +++++++++---------- astroid/builder.py | 2 ++ astroid/raw_building.py | 10 +++++++++- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index 22fce54883..418149d85e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,11 @@ Release date: TBA * Reduce file system access in ``ast_from_file()``. +* Reduce time to ``import astroid`` by delaying ``astroid_bootstrapping()`` until + the first instantiation of ``AstroidBuilder``. + + Closes #2161 + * Fix incorrect cache keys for inference results, thereby correctly inferring types for calls instantiating types dynamically. @@ -31,7 +36,7 @@ Release date: TBA We have tried to minimize the amount of breaking changes caused by this change but some are unavoidable. -* ``infer_call_result`` now shares the same interface across all implemenations. Namely: +* ``infer_call_result`` now shares the same interface across all implementations. Namely: ```python def infer_call_result( self, diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index efca0c5ed7..9cd6304e63 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -129,12 +129,14 @@ def _extend_builtins(class_transforms): transform(builtin_ast[class_name]) -_extend_builtins( - { - "bytes": partial(_extend_string_class, code=BYTES_CLASS, rvalue="b''"), - "str": partial(_extend_string_class, code=STR_CLASS, rvalue="''"), - } -) +def on_bootstrap(): + """Called by astroid_bootstrapping().""" + _extend_builtins( + { + "bytes": partial(_extend_string_class, code=BYTES_CLASS, rvalue="b''"), + "str": partial(_extend_string_class, code=STR_CLASS, rvalue="''"), + } + ) def _builtin_filter_predicate(node, builtin_name) -> bool: diff --git a/astroid/brain/brain_nose.py b/astroid/brain/brain_nose.py index e668f32906..83078fa817 100644 --- a/astroid/brain/brain_nose.py +++ b/astroid/brain/brain_nose.py @@ -7,13 +7,12 @@ import re import textwrap -import astroid.builder +from astroid.bases import BoundMethod from astroid.brain.helpers import register_module_extender +from astroid.builder import AstroidBuilder from astroid.exceptions import InferenceError from astroid.manager import AstroidManager - -_BUILDER = astroid.builder.AstroidBuilder(AstroidManager()) - +from astroid.nodes import List, Module CAPITALS = re.compile("([A-Z])") @@ -24,7 +23,7 @@ def _pep8(name, caps=CAPITALS): def _nose_tools_functions(): """Get an iterator of names and bound methods.""" - module = _BUILDER.string_build( + module = AstroidBuilder().string_build( textwrap.dedent( """ import unittest @@ -42,10 +41,10 @@ class Test(unittest.TestCase): for method in case.methods(): if method.name.startswith("assert") and "_" not in method.name: pep8_name = _pep8(method.name) - yield pep8_name, astroid.BoundMethod(method, case) + yield pep8_name, BoundMethod(method, case) if method.name == "assertEqual": # nose also exports assert_equals. - yield "assert_equals", astroid.BoundMethod(method, case) + yield "assert_equals", BoundMethod(method, case) def _nose_tools_transform(node): @@ -55,7 +54,7 @@ def _nose_tools_transform(node): def _nose_tools_trivial_transform(): """Custom transform for the nose.tools module.""" - stub = _BUILDER.string_build("""__all__ = []""") + stub = AstroidBuilder().string_build("""__all__ = []""") all_entries = ["ok_", "eq_"] for pep8_name, method in _nose_tools_functions(): @@ -65,7 +64,7 @@ def _nose_tools_trivial_transform(): # Update the __all__ variable, since nose.tools # does this manually with .append. all_assign = stub["__all__"].parent - all_object = astroid.List(all_entries) + all_object = List(all_entries) all_object.parent = all_assign all_assign.value = all_object return stub @@ -75,5 +74,5 @@ def _nose_tools_trivial_transform(): AstroidManager(), "nose.tools.trivial", _nose_tools_trivial_transform ) AstroidManager().register_transform( - astroid.Module, _nose_tools_transform, lambda n: n.name == "nose.tools" + Module, _nose_tools_transform, lambda n: n.name == "nose.tools" ) diff --git a/astroid/builder.py b/astroid/builder.py index 90f211be89..d654cea77b 100644 --- a/astroid/builder.py +++ b/astroid/builder.py @@ -69,6 +69,8 @@ def __init__( ) -> None: super().__init__(manager) self._apply_transforms = apply_transforms + if not raw_building.InspectBuilder.bootstrapped: + raw_building._astroid_bootstrapping() def module_build( self, module: types.ModuleType, modname: str | None = None diff --git a/astroid/raw_building.py b/astroid/raw_building.py index 45eeb10bc0..bf07028e2b 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -427,6 +427,8 @@ class InspectBuilder: FunctionDef and ClassDef nodes and some others as guessed. """ + bootstrapped: bool = False + def __init__(self, manager_instance: AstroidManager | None = None) -> None: self._manager = manager_instance or AstroidManager() self._done: dict[types.ModuleType | type, nodes.Module | nodes.ClassDef] = {} @@ -725,5 +727,11 @@ def _astroid_bootstrapping() -> None: builder.object_build(klass, _type) astroid_builtin[_type.__name__] = klass + InspectBuilder.bootstrapped = True + + # pylint: disable-next=import-outside-toplevel + from astroid.brain.brain_builtin_inference import on_bootstrap -_astroid_bootstrapping() + # Instantiates an AstroidBuilder(), which is where + # InspectBuilder.bootstrapped is checked, so place after bootstrapped=True. + on_bootstrap()