Skip to content

Commit 642e42a

Browse files
authored
Lazy import black and isort for faster test startup (#2556)
* Refactor: Optimize black and isort import handling with lazy loading * Refactor: Add lazy import for inflect and clean up black mode import handling * Refactor: Add lazy imports for black and isort in conftest.py * Refactor: Improve isort configuration handling in apply_isort method
1 parent 43d4990 commit 642e42a

3 files changed

Lines changed: 53 additions & 34 deletions

File tree

src/datamodel_code_generator/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from urllib.parse import ParseResult, urlparse
1616

1717
import argcomplete
18-
import black
1918
from pydantic import BaseModel
2019
from typing_extensions import TypeAlias
2120

@@ -35,6 +34,7 @@
3534
Formatter,
3635
PythonVersion,
3736
PythonVersionMin,
37+
_get_black,
3838
is_supported_in_black,
3939
)
4040
from datamodel_code_generator.model.pydantic_v2 import UnionMode # noqa: TC001 # needed for pydantic
@@ -518,7 +518,7 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
518518
print( # noqa: T201
519519
f"Installed black doesn't support Python version {config.target_python_version.value}.\n"
520520
f"You have to install a newer black.\n"
521-
f"Installed black version: {black.__version__}",
521+
f"Installed black version: {_get_black().__version__}",
522522
file=sys.stderr,
523523
)
524524
return Exit.ERROR

src/datamodel_code_generator/format.py

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,41 @@
88

99
import subprocess # noqa: S404
1010
from enum import Enum
11-
from functools import cached_property
11+
from functools import cached_property, lru_cache
1212
from importlib import import_module
1313
from pathlib import Path
1414
from typing import TYPE_CHECKING, Any
1515
from warnings import warn
1616

17-
import black
18-
import isort
19-
2017
from datamodel_code_generator.util import load_toml
2118

2219
if TYPE_CHECKING:
2320
from collections.abc import Sequence
24-
try:
25-
import black.mode
26-
except ImportError: # pragma: no cover
27-
black.mode = None
21+
22+
23+
@lru_cache(maxsize=1)
24+
def _get_black() -> Any:
25+
import black as _black # noqa: PLC0415
26+
27+
return _black
28+
29+
30+
@lru_cache(maxsize=1)
31+
def _get_black_mode() -> Any: # pragma: no cover
32+
black = _get_black()
33+
try:
34+
import black.mode # noqa: PLC0415
35+
except ImportError:
36+
return None
37+
else:
38+
return black.mode
39+
40+
41+
@lru_cache(maxsize=1)
42+
def _get_isort() -> Any:
43+
import isort as _isort # noqa: PLC0415
44+
45+
return _isort
2846

2947

3048
class DatetimeClassType(Enum):
@@ -91,16 +109,19 @@ def has_strenum(self) -> bool:
91109
PythonVersionMin = PythonVersion.PY_39
92110

93111

94-
BLACK_PYTHON_VERSION: dict[PythonVersion, black.TargetVersion] = {
95-
v: getattr(black.TargetVersion, f"PY{v.name.split('_')[-1]}")
96-
for v in PythonVersion
97-
if hasattr(black.TargetVersion, f"PY{v.name.split('_')[-1]}")
98-
}
112+
@lru_cache(maxsize=1)
113+
def _get_black_python_version_map() -> dict[PythonVersion, Any]:
114+
black = _get_black()
115+
return {
116+
v: getattr(black.TargetVersion, f"PY{v.name.split('_')[-1]}")
117+
for v in PythonVersion
118+
if hasattr(black.TargetVersion, f"PY{v.name.split('_')[-1]}")
119+
}
99120

100121

101122
def is_supported_in_black(python_version: PythonVersion) -> bool: # pragma: no cover
102123
"""Check if a Python version is supported by the installed black version."""
103-
return python_version in BLACK_PYTHON_VERSION
124+
return python_version in _get_black_python_version_map()
104125

105126

106127
def black_find_project_root(sources: Sequence[Path]) -> Path:
@@ -153,6 +174,10 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0917
153174
else:
154175
config = {}
155176

177+
black = _get_black()
178+
black_mode = _get_black_mode()
179+
isort = _get_isort()
180+
156181
black_kwargs: dict[str, Any] = {}
157182
if wrap_string_literal is not None:
158183
experimental_string_processing = wrap_string_literal
@@ -175,10 +200,10 @@ def __init__( # noqa: PLR0912, PLR0913, PLR0917
175200
elif experimental_string_processing:
176201
black_kwargs["preview"] = True
177202
black_kwargs["unstable"] = config.get("unstable", False)
178-
black_kwargs["enabled_features"] = {black.mode.Preview.string_processing}
203+
black_kwargs["enabled_features"] = {black_mode.Preview.string_processing}
179204

180205
self.black_mode = black.FileMode(
181-
target_versions={BLACK_PYTHON_VERSION[python_version]},
206+
target_versions={_get_black_python_version_map()[python_version]},
182207
line_length=config.get("line-length", black.DEFAULT_LINE_LENGTH),
183208
string_normalization=not skip_string_normalization or not config.get("skip-string-normalization", True),
184209
**black_kwargs,
@@ -246,6 +271,7 @@ def format_code(
246271

247272
def apply_black(self, code: str) -> str:
248273
"""Format code using black."""
274+
black = _get_black()
249275
return black.format_str(
250276
code,
251277
mode=self.black_mode,
@@ -271,27 +297,16 @@ def apply_ruff_formatter(self, code: str) -> str:
271297
)
272298
return result.stdout.decode(self.encoding)
273299

274-
if TYPE_CHECKING:
275-
276-
def apply_isort(self, code: str) -> str:
277-
"""Sort imports using isort."""
278-
...
279-
280-
elif isort.__version__.startswith("4."):
281-
282-
def apply_isort(self, code: str) -> str:
283-
"""Sort imports using isort v4."""
300+
def apply_isort(self, code: str) -> str:
301+
"""Sort imports using isort."""
302+
isort = _get_isort()
303+
if self.isort_config is None: # pragma: no cover
284304
return isort.SortImports(
285305
file_contents=code,
286306
settings_path=self.settings_path,
287307
**self.isort_config_kwargs,
288308
).output
289-
290-
else:
291-
292-
def apply_isort(self, code: str) -> str:
293-
"""Sort imports using isort v5+."""
294-
return isort.code(code, config=self.isort_config)
309+
return isort.code(code, config=self.isort_config)
295310

296311

297312
class CustomCodeFormatter:

tests/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,8 @@ def _preload_heavy_modules() -> None:
241241
This reduces per-test overhead when running with pytest-xdist,
242242
as each worker only pays the import cost once at session start.
243243
"""
244+
import black # noqa: PLC0415, F401
245+
import inflect # noqa: PLC0415, F401
246+
import isort # noqa: PLC0415, F401
247+
244248
import datamodel_code_generator # noqa: PLC0415, F401

0 commit comments

Comments
 (0)