From 74d0d0f13b236adebab91c520745f54fff4c0c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20=C5=9Eafak?= <3928300+esafak@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:01:01 +0000 Subject: [PATCH 1/2] refactor: Decouple discovery by duplicating info utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change decouples the discovery module from the main virtualenv module by duplicating utility functions from `virtualenv.info` into the `virtualenv.discovery` module. - A new `virtualenv.discovery.info` module was created with a copy of `fs_is_case_sensitive`, `fs_path_id`, and `IS_WIN`. - `virtualenv.info` is left unchanged to maintain its functionality for the core library. - The discovery-related files (`py_info.py`, `builtin.py`) now use their own private copy of these utilities from `discovery.info`. - The dependency of `cached_py_info.py` on `virtualenv.util.subprocess` was removed. This approach uses code duplication to achieve a clean separation, preparing the `discovery` module for extraction into its own package without creating a hard dependency from the core library. Signed-off-by: Emre Şafak <3928300+esafak@users.noreply.github.com> --- docs/changelog/2074d.feature.rst | 1 + src/virtualenv/discovery/builtin.py | 3 +- src/virtualenv/discovery/cached_py_info.py | 5 ++-- src/virtualenv/discovery/info.py | 33 ++++++++++++++++++++++ src/virtualenv/discovery/py_info.py | 3 +- 5 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 docs/changelog/2074d.feature.rst create mode 100644 src/virtualenv/discovery/info.py diff --git a/docs/changelog/2074d.feature.rst b/docs/changelog/2074d.feature.rst new file mode 100644 index 000000000..3ee47a835 --- /dev/null +++ b/docs/changelog/2074d.feature.rst @@ -0,0 +1 @@ +Decouple discovery by duplicating info utils - by :user:`esafak`. diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py index 841d36b16..8c2b0be81 100644 --- a/src/virtualenv/discovery/builtin.py +++ b/src/virtualenv/discovery/builtin.py @@ -9,9 +9,8 @@ from platformdirs import user_data_path -from virtualenv.info import IS_WIN, fs_path_id - from .discover import Discover +from .info import IS_WIN, fs_path_id from .py_info import PythonInfo from .py_spec import PythonSpec diff --git a/src/virtualenv/discovery/cached_py_info.py b/src/virtualenv/discovery/cached_py_info.py index 9bc463667..f0a1dc609 100644 --- a/src/virtualenv/discovery/cached_py_info.py +++ b/src/virtualenv/discovery/cached_py_info.py @@ -12,12 +12,12 @@ import logging import os import random +import subprocess import sys from collections import OrderedDict from pathlib import Path from shlex import quote from string import ascii_lowercase, ascii_uppercase, digits -from subprocess import Popen from typing import TYPE_CHECKING from virtualenv.app_data.na import AppDataDisabled @@ -27,7 +27,6 @@ from virtualenv.app_data.base import AppData from virtualenv.cache import Cache from virtualenv.discovery.py_info import PythonInfo -from virtualenv.util.subprocess import subprocess _CACHE = OrderedDict() _CACHE[Path(sys.executable)] = PythonInfo() @@ -145,7 +144,7 @@ def _run_subprocess(cls, exe, app_data, env): env.pop("__PYVENV_LAUNCHER__", None) LOGGER.debug("get interpreter info via cmd: %s", LogCmd(cmd)) try: - process = Popen( + process = subprocess.Popen( cmd, universal_newlines=True, stdin=subprocess.PIPE, diff --git a/src/virtualenv/discovery/info.py b/src/virtualenv/discovery/info.py new file mode 100644 index 000000000..c786a6a98 --- /dev/null +++ b/src/virtualenv/discovery/info.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import logging +import os +import sys +import tempfile + +_FS_CASE_SENSITIVE = None +LOGGER = logging.getLogger(__name__) +IS_WIN = sys.platform == "win32" + + +def fs_is_case_sensitive(): + """Check if the file system is case-sensitive.""" + global _FS_CASE_SENSITIVE # noqa: PLW0603 + + if _FS_CASE_SENSITIVE is None: + with tempfile.NamedTemporaryFile(prefix="TmP") as tmp_file: + _FS_CASE_SENSITIVE = not os.path.exists(tmp_file.name.lower()) + LOGGER.debug("filesystem is %scase-sensitive", "" if _FS_CASE_SENSITIVE else "not ") + return _FS_CASE_SENSITIVE + + +def fs_path_id(path: str) -> str: + """Get a case-normalized path identifier.""" + return path.casefold() if fs_is_case_sensitive() else path + + +__all__ = ( + "IS_WIN", + "fs_is_case_sensitive", + "fs_path_id", +) diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py index f12e2c5cd..fe7e6f46b 100644 --- a/src/virtualenv/discovery/py_info.py +++ b/src/virtualenv/discovery/py_info.py @@ -18,6 +18,8 @@ from collections import OrderedDict, namedtuple from string import digits +from .info import fs_is_case_sensitive + VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"]) # noqa: PYI024 LOGGER = logging.getLogger(__name__) @@ -659,7 +661,6 @@ def _possible_base(self): for base in possible_base: lower = base.lower() yield lower - from virtualenv.info import fs_is_case_sensitive # noqa: PLC0415 if fs_is_case_sensitive(): if base != lower: From 1b1fa7b289ec8333ebbfc2998aa4ea4dd81cdeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emre=20=C5=9Eafak?= <3928300+esafak@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:19:43 -0400 Subject: [PATCH 2/2] So that's why it was imported locally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Emre Şafak <3928300+esafak@users.noreply.github.com> --- src/virtualenv/discovery/py_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py index fe7e6f46b..5f16dbc8a 100644 --- a/src/virtualenv/discovery/py_info.py +++ b/src/virtualenv/discovery/py_info.py @@ -18,8 +18,6 @@ from collections import OrderedDict, namedtuple from string import digits -from .info import fs_is_case_sensitive - VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"]) # noqa: PYI024 LOGGER = logging.getLogger(__name__) @@ -662,6 +660,8 @@ def _possible_base(self): lower = base.lower() yield lower + from .info import fs_is_case_sensitive # noqa: PLC0415 + if fs_is_case_sensitive(): if base != lower: yield base