Skip to content

Commit 22a9496

Browse files
committed
More conversions to pathlib to help with the pythonfinder integration
1 parent 40deaf7 commit 22a9496

19 files changed

+644
-319
lines changed

pipenv/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import os
33
import sys
44
import warnings
5+
from pathlib import Path
56

67
# This has to come before imports of pipenv
7-
PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
8-
PIP_ROOT = os.sep.join([PIPENV_ROOT, "patched", "pip"])
9-
sys.path.insert(0, PIPENV_ROOT)
8+
PIPENV_ROOT = Path(__file__).resolve().parent.absolute()
9+
PIP_ROOT = str(PIPENV_ROOT / "patched" / "pip")
10+
sys.path.insert(0, str(PIPENV_ROOT))
1011
sys.path.insert(0, PIP_ROOT)
1112

1213
# Load patched pip instead of system pip
@@ -15,9 +16,10 @@
1516

1617
def _ensure_modules():
1718
# Ensure when pip gets invoked it uses our patched version
19+
location = Path(__file__).parent / "patched" / "pip" / "__init__.py"
1820
spec = importlib.util.spec_from_file_location(
1921
"pip",
20-
location=os.path.join(os.path.dirname(__file__), "patched", "pip", "__init__.py"),
22+
location=str(location),
2123
)
2224
pip = importlib.util.module_from_spec(spec)
2325
sys.modules["pip"] = pip

pipenv/cli/command.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import sys
3+
from pathlib import Path
34

45
from pipenv import environments
56
from pipenv.__version__ import __version__
@@ -98,8 +99,8 @@ def cli(
9899

99100
if man:
100101
if system_which("man"):
101-
path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pipenv.1")
102-
os.execle(system_which("man"), "man", path, os.environ)
102+
path = Path(__file__).parent.parent / "pipenv.1"
103+
os.execle(system_which("man"), "man", str(path), os.environ)
103104
return 0
104105
else:
105106
err.print(
@@ -393,8 +394,8 @@ def shell(state, fancy=False, shell_args=None, anyway=False, quiet=False):
393394
# Use fancy mode for Windows or pwsh on *nix.
394395
if (
395396
os.name == "nt"
396-
or (os.environ.get("PIPENV_SHELL") or "").split(os.path.sep)[-1] == "pwsh"
397-
or (os.environ.get("SHELL") or "").split(os.path.sep)[-1] == "pwsh"
397+
or Path(os.environ.get("PIPENV_SHELL") or "").name == "pwsh"
398+
or Path(os.environ.get("SHELL") or "").name == "pwsh"
398399
):
399400
fancy = True
400401
do_shell(
@@ -624,7 +625,7 @@ def run_open(state, module, *args, **kwargs):
624625
console.print("Module not found!", style="red")
625626
sys.exit(1)
626627
if "__init__.py" in c.stdout:
627-
p = os.path.dirname(c.stdout.strip().rstrip("cdo"))
628+
p = Path(c.stdout.strip().rstrip("cdo")).parent
628629
else:
629630
p = c.stdout.strip().rstrip("cdo")
630631
console.print(f"Opening {p!r} in your EDITOR.", style="bold")

pipenv/cli/options.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import os
21
import re
2+
from pathlib import Path
33

44
from pipenv.project import Project
55
from pipenv.utils import console, err
@@ -460,7 +460,8 @@ def validate_python_path(ctx, param, value):
460460
# the path or an absolute path. To report errors as early as possible
461461
# we'll report absolute paths which do not exist:
462462
if isinstance(value, (str, bytes)):
463-
if os.path.isabs(value) and not os.path.isfile(value):
463+
path = Path(value)
464+
if path.is_absolute() and not path.is_file():
464465
raise BadParameter(f"Expected Python at path {value} does not exist")
465466
return value
466467

pipenv/environment.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def python_version(self) -> str | None:
113113
@property
114114
def python_info(self) -> dict[str, str]:
115115
include_dir = self.prefix / "include"
116-
if not os.path.exists(include_dir):
116+
if not include_dir.exists():
117117
include_dirs = self.get_include_path()
118118
if include_dirs:
119119
include_path = include_dirs.get(
@@ -130,15 +130,17 @@ def python_info(self) -> dict[str, str]:
130130
return {}
131131

132132
def _replace_parent_version(self, path: str, replace_version: str) -> str:
133-
if not os.path.exists(path):
134-
base, leaf = os.path.split(path)
135-
base, parent = os.path.split(base)
136-
leaf = os.path.join(parent, leaf).replace(
133+
path_obj = Path(path)
134+
if not path_obj.exists():
135+
parent = path_obj.parent
136+
grandparent = parent.parent
137+
leaf = f"{parent.name}/{path_obj.name}"
138+
leaf = leaf.replace(
137139
replace_version,
138140
self.python_info.get("py_version_short", get_python_version()),
139141
)
140-
return os.path.join(base, leaf)
141-
return path
142+
return str(grandparent / leaf)
143+
return str(path_obj)
142144

143145
@cached_property
144146
def install_scheme(self):

pipenv/project.py

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def __init__(self, python_version=None, chdir=True):
152152
self._pipfile_newlines = DEFAULT_NEWLINES
153153
self._lockfile_newlines = DEFAULT_NEWLINES
154154
self._requirements_location = None
155-
self._original_dir = os.path.abspath(os.curdir)
155+
self._original_dir = Path.cwd().resolve()
156156
self._environment = None
157157
self._build_system = {"requires": ["setuptools", "wheel"]}
158158
self.python_version = python_version
@@ -692,11 +692,12 @@ def _parse_pipfile(
692692
return toml.loads(contents)
693693

694694
def _read_pyproject(self) -> None:
695-
pyproject = self.path_to("pyproject.toml")
696-
if os.path.exists(pyproject):
697-
self._pyproject = toml.load(pyproject)
695+
pyproject_path = Path(self.path_to("pyproject.toml"))
696+
if pyproject_path.exists():
697+
self._pyproject = toml.load(pyproject_path)
698698
build_system = self._pyproject.get("build-system", None)
699-
if not os.path.exists(self.path_to("setup.py")):
699+
setup_py_path = Path(self.path_to("setup.py"))
700+
if not setup_py_path.exists():
700701
if not build_system or not build_system.get("requires"):
701702
build_system = {
702703
"requires": ["setuptools>=40.8.0", "wheel"],
@@ -783,7 +784,7 @@ def lockfile_location(self):
783784

784785
@property
785786
def lockfile_exists(self):
786-
return os.path.isfile(self.lockfile_location)
787+
return Path(self.lockfile_location).is_file()
787788

788789
@property
789790
def lockfile_content(self):
@@ -987,9 +988,7 @@ def pipfile_sources(self, expand_vars=True):
987988
if os.environ.get("PIPENV_PYPI_MIRROR"):
988989
sources[0]["url"] = os.environ["PIPENV_PYPI_MIRROR"]
989990
return sources
990-
# We need to make copies of the source info so we don't
991-
# accidentally modify the cache. See #2100 where values are
992-
# written after the os.path.expandvars() call.
991+
# Expand environment variables in the source URLs.
993992
sources = [
994993
{k: safe_expandvars(v) if expand_vars else v for k, v in source.items()}
995994
for source in self.parsed_pipfile["source"]
@@ -1320,25 +1319,34 @@ def recase_pipfile(self):
13201319

13211320
def load_lockfile(self, expand_env_vars=True):
13221321
lockfile_modified = False
1323-
with open(self.lockfile_location, encoding="utf-8") as lock:
1324-
try:
1325-
j = json.load(lock)
1326-
self._lockfile_newlines = preferred_newlines(lock)
1327-
except JSONDecodeError:
1328-
err.print(
1329-
"[bold yellow]Pipfile.lock is corrupted; ignoring contents.[/bold yellow]"
1330-
)
1331-
j = {}
1322+
lockfile_path = Path(self.lockfile_location)
1323+
pipfile_path = Path(self.pipfile_location)
1324+
1325+
try:
1326+
with lockfile_path.open(encoding="utf-8") as lock:
1327+
try:
1328+
j = json.load(lock)
1329+
self._lockfile_newlines = preferred_newlines(lock)
1330+
except JSONDecodeError:
1331+
err.print(
1332+
"[bold yellow]Pipfile.lock is corrupted; ignoring contents.[/bold yellow]"
1333+
)
1334+
j = {}
1335+
except FileNotFoundError:
1336+
j = {}
1337+
13321338
if not j.get("_meta"):
1333-
with open(self.pipfile_location) as pf:
1339+
with pipfile_path.open() as pf:
13341340
default_lockfile = plette.Lockfile.with_meta_from(
13351341
plette.Pipfile.load(pf), categories=[]
13361342
)
13371343
j["_meta"] = default_lockfile._data["_meta"]
13381344
lockfile_modified = True
1345+
13391346
if j.get("default") is None:
13401347
j["default"] = {}
13411348
lockfile_modified = True
1349+
13421350
if j.get("develop") is None:
13431351
j["develop"] = {}
13441352
lockfile_modified = True
@@ -1349,14 +1357,16 @@ def load_lockfile(self, expand_env_vars=True):
13491357
if expand_env_vars:
13501358
# Expand environment variables in Pipfile.lock at runtime.
13511359
for i, _ in enumerate(j["_meta"].get("sources", {})):
1360+
# Path doesn't have expandvars method, so we need to use os.path.expandvars
13521361
j["_meta"]["sources"][i]["url"] = os.path.expandvars(
13531362
j["_meta"]["sources"][i]["url"]
13541363
)
13551364

13561365
return j
13571366

13581367
def get_lockfile_hash(self):
1359-
if not os.path.exists(self.lockfile_location):
1368+
lockfile_path = Path(self.lockfile_location)
1369+
if not lockfile_path.exists():
13601370
return
13611371

13621372
try:
@@ -1443,22 +1453,35 @@ def _which(self, command, location=None, allow_global=False):
14431453
location = self.virtualenv_location
14441454
else:
14451455
location = os.environ.get("VIRTUAL_ENV", None)
1446-
if not (location and Path(location).exists()) and not allow_global:
1456+
1457+
location_path = Path(location) if location else None
1458+
1459+
if not (location_path and location_path.exists()) and not allow_global:
14471460
raise RuntimeError("location not created nor specified")
14481461

1449-
version_str = "python{}".format(".".join([str(v) for v in sys.version_info[:2]]))
1450-
is_python = command in ("python", os.path.basename(sys.executable), version_str)
1462+
version_str = f"python{'.'.join([str(v) for v in sys.version_info[:2]])}"
1463+
is_python = command in ("python", Path(sys.executable).name, version_str)
1464+
14511465
if not allow_global:
14521466
if os.name == "nt":
1453-
p = find_windows_executable(os.path.join(location, "Scripts"), command)
1467+
p = find_windows_executable(str(location_path / "Scripts"), command)
1468+
# Convert to Path object if it's a string
1469+
p = Path(p) if isinstance(p, str) else p
14541470
else:
1455-
p = Path(location) / "bin" / command
1471+
p = location_path / "bin" / command
14561472
elif is_python:
14571473
p = Path(sys.executable)
1458-
p_str = str(p) if isinstance(p, Path) else p
1459-
if not os.path.exists(p_str):
1474+
else:
1475+
p = None
1476+
1477+
if p is None or not p.exists():
14601478
if is_python:
1461-
p = sys.executable or system_which("python")
1479+
p = (
1480+
Path(sys.executable)
1481+
if sys.executable
1482+
else Path(system_which("python"))
1483+
)
14621484
else:
1463-
p = system_which(command)
1485+
p = Path(system_which(command)) if system_which(command) else None
1486+
14641487
return p

pipenv/routines/shell.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def do_run(project, command, args, python=False, pypi_mirror=None):
6060
6161
Args are appended to the command in [scripts] section of project if found.
6262
"""
63+
from pathlib import Path
64+
6365
from pipenv.cmdparse import ScriptEmptyError
6466

6567
env = os.environ.copy()
@@ -74,14 +76,13 @@ def do_run(project, command, args, python=False, pypi_mirror=None):
7476

7577
path = env.get("PATH", "")
7678
if project.virtualenv_location:
77-
new_path = os.path.join(
78-
project.virtualenv_location, "Scripts" if os.name == "nt" else "bin"
79-
)
80-
paths = path.split(os.pathsep)
81-
paths.insert(0, new_path)
82-
path = os.pathsep.join(paths)
83-
env["VIRTUAL_ENV"] = project.virtualenv_location
84-
env["PATH"] = path
79+
virtualenv_path = Path(project.virtualenv_location)
80+
bin_dir = "Scripts" if os.name == "nt" else "bin"
81+
new_path = str(virtualenv_path / bin_dir)
82+
env["PATH"] = f"{new_path}{os.pathsep}{path}"
83+
env["VIRTUAL_ENV"] = str(virtualenv_path)
84+
else:
85+
env["PATH"] = str(path)
8586

8687
# Set an environment variable, so we know we're in the environment.
8788
# Only set PIPENV_ACTIVE after finishing reading virtualenv_location
@@ -135,7 +136,7 @@ def do_run_posix(project, script, command, env):
135136
sys.exit(1)
136137
os.execve(
137138
command_path,
138-
[command_path, *(os.path.expandvars(arg) for arg in script.args)],
139+
[command_path, *(expandvars(arg) for arg in script.args)],
139140
env,
140141
)
141142

pipenv/shells.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616

1717
def _build_info(value):
18-
return (os.path.splitext(os.path.basename(value))[0], value)
18+
path = Path(value)
19+
return (path.stem, value)
1920

2021

2122
def detect_info(project):
@@ -86,25 +87,25 @@ def __repr__(self):
8687

8788
@contextlib.contextmanager
8889
def inject_path(self, venv):
90+
venv_path = Path(venv)
8991
with temp_environ():
90-
os.environ["PATH"] = "{}{}{}".format(
91-
os.pathsep.join(str(p.parent) for p in _iter_python(venv)),
92-
os.pathsep,
93-
os.environ["PATH"],
92+
os.environ["PATH"] = (
93+
f"{os.pathsep.join(str(p.parent) for p in _iter_python(venv_path))}{os.pathsep}{os.environ['PATH']}"
9494
)
9595
yield
9696

9797
def fork(self, venv, cwd, args):
9898
# FIXME: This isn't necessarily the correct prompt. We should read the
9999
# actual prompt by peeking into the activation script.
100-
name = os.path.basename(venv)
101-
os.environ["VIRTUAL_ENV"] = str(venv)
100+
venv_path = Path(venv)
101+
name = venv_path.name
102+
os.environ["VIRTUAL_ENV"] = str(venv_path)
102103
if "PROMPT" in os.environ:
103-
os.environ["PROMPT"] = "({}) {}".format(name, os.environ["PROMPT"])
104+
os.environ["PROMPT"] = f"({name}) {os.environ['PROMPT']}"
104105
if "PS1" in os.environ:
105-
os.environ["PS1"] = "({}) {}".format(name, os.environ["PS1"])
106+
os.environ["PS1"] = f"({name}) {os.environ['PS1']}"
106107
with self.inject_path(venv):
107-
os.chdir(cwd)
108+
os.chdir(str(cwd) if isinstance(cwd, Path) else cwd)
108109
_handover(self.cmd, self.args + list(args))
109110

110111
def fork_compat(self, venv, cwd, args):
@@ -187,16 +188,16 @@ def fork(self, venv, cwd, args):
187188

188189
class CmderCommandPrompt(CmderEmulatedShell):
189190
def fork(self, venv, cwd, args):
190-
rc = os.path.expandvars("%CMDER_ROOT%\\vendor\\init.bat")
191-
if os.path.exists(rc):
192-
self.args.extend(["/k", rc])
191+
rc_path = Path(os.path.expandvars("%CMDER_ROOT%\\vendor\\init.bat"))
192+
if rc_path.exists():
193+
self.args.extend(["/k", str(rc_path)])
193194
super().fork(venv, cwd, args)
194195

195196

196197
class CmderPowershell(Shell):
197198
def fork(self, venv, cwd, args):
198-
rc = os.path.expandvars("%CMDER_ROOT%\\vendor\\profile.ps1")
199-
if os.path.exists(rc):
199+
rc_path = Path(os.path.expandvars("%CMDER_ROOT%\\vendor\\profile.ps1"))
200+
if rc_path.exists():
200201
self.args.extend(
201202
[
202203
"-ExecutionPolicy",
@@ -205,7 +206,7 @@ def fork(self, venv, cwd, args):
205206
"-NoProfile",
206207
"-NoExit",
207208
"-Command",
208-
f"Invoke-Expression '. ''{rc}'''",
209+
f"Invoke-Expression '. ''{rc_path}'''",
209210
]
210211
)
211212
super().fork(venv, cwd, args)

0 commit comments

Comments
 (0)