Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1547.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deferred the import of `inspect` and the loading of the `validators` and `converters` submodules until first use, reducing `import attr`/`import attrs` time by ~25%.
18 changes: 17 additions & 1 deletion src/attr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Classes Without Boilerplate
"""

import sys

from functools import partial
from typing import Callable, Literal, Protocol

from . import converters, exceptions, filters, setters, validators
from . import exceptions, filters, setters
from ._cmp import cmp_using
from ._config import get_run_validators, set_run_validators
from ._funcs import asdict, assoc, astuple, has, resolve_types
Expand Down Expand Up @@ -78,13 +80,23 @@ class AttrsInstance(Protocol):
]


_LAZY_SUBMODULES = {"converters", "validators"}


def _make_getattr(mod_name: str) -> Callable:
"""
Create a metadata proxy for packaging information that uses *mod_name* in
its warnings and errors.
"""

def __getattr__(name: str) -> str:
if name in _LAZY_SUBMODULES:
import importlib

mod = importlib.import_module(f".{name}", mod_name)
sys.modules[mod_name].__dict__[name] = mod
return mod

if name not in ("__version__", "__version_info__"):
msg = f"module {mod_name} has no attribute {name}"
raise AttributeError(msg)
Expand All @@ -102,3 +114,7 @@ def __getattr__(name: str) -> str:


__getattr__ = _make_getattr(__name__)


def __dir__() -> list[str]:
return __all__
7 changes: 6 additions & 1 deletion src/attr/_compat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# SPDX-License-Identifier: MIT

import inspect
import platform
import sys
import threading
Expand Down Expand Up @@ -46,6 +45,8 @@ class _AnnotationExtractor:
__slots__ = ["sig"]

def __init__(self, callable):
import inspect

try:
self.sig = inspect.signature(callable)
except (ValueError, TypeError): # inspect failed
Expand All @@ -55,6 +56,8 @@ def get_first_param_type(self):
"""
Return the type annotation of the first argument if it's not empty.
"""
import inspect

if not self.sig:
return None

Expand All @@ -68,6 +71,8 @@ def get_return_type(self):
"""
Return the return type if it's not empty.
"""
import inspect

if (
self.sig
and self.sig.return_annotation is not inspect.Signature.empty
Expand Down
5 changes: 4 additions & 1 deletion src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import contextlib
import copy
import enum
import inspect
import itertools
import linecache
import sys
Expand Down Expand Up @@ -705,6 +704,8 @@ def __init__(
if self._has_pre_init:
# Check if the pre init method has more arguments than just `self`
# We want to pass arguments if pre init expects arguments
import inspect

pre_init_func = cls.__attrs_pre_init__
pre_init_signature = inspect.signature(pre_init_func)
self._pre_init_has_args = len(pre_init_signature.parameters) > 1
Expand Down Expand Up @@ -920,6 +921,8 @@ def _create_slots_class(self):
# To know to update them.
additional_closure_functions_to_update = []
if cached_properties:
import inspect

class_annotations = _get_annotations(self._cls)
for name, func in cached_properties.items():
# Add cached properties to names for slotting.
Expand Down
6 changes: 5 additions & 1 deletion src/attrs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from attr._make import ClassProps
from attr._next_gen import asdict, astuple, inspect

from . import converters, exceptions, filters, setters, validators
from . import exceptions, filters, setters


__all__ = [
Expand Down Expand Up @@ -70,3 +70,7 @@
]

__getattr__ = _make_getattr(__name__)


def __dir__() -> list[str]:
return __all__
19 changes: 19 additions & 0 deletions tests/test_import.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# SPDX-License-Identifier: MIT

import attr
import attrs


class TestImportStar:
def test_from_attr_import_star(self):
Expand All @@ -9,3 +12,19 @@ def test_from_attr_import_star(self):
# attr_import_star contains `from attr import *`, which cannot
# be done here because *-imports are only allowed on module level.
from . import attr_import_star # noqa: F401


class TestDir:
def test_attr_dir_includes_lazy_submodules(self):
"""
converters and validators are listed in dir(attr).
"""
assert "converters" in dir(attr)
assert "validators" in dir(attr)

def test_attrs_dir_includes_lazy_submodules(self):
"""
converters and validators are listed in dir(attrs).
"""
assert "converters" in dir(attrs)
assert "validators" in dir(attrs)