Coverage for src / library_skills / python_env.py: 100%
94 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-03 20:19 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-03 20:19 +0000
1import os 2tcucvcwcxcyczcAcBc
2from pathlib import Path 2tcucvcwcxcyczcAcBc
4from .workspace import find_node_workspace, find_uv_workspace 2tcucvcwcxcyczcAcBc
7def find_project_root(cwd: Path) -> Path | None: 2tcucvcwcxcyczcAcBc
8 """Find the nearest Python project/environment root walking up from cwd."""
9 workspace = find_uv_workspace(cwd) 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abCcDcTbb Yb|bic}b*bUb_b[bPcbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbEcFcVbc Zb~bjcac?bWb`b]bQcNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M lcmcRba Xb{bkcbc8bSb^b@bRcPbQb
10 if workspace is not None: 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abCcDcTbb Yb|bic}b*bUb_b[bPcbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbEcFcVbc Zb~bjcac?bWb`b]bQcNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M lcmcRba Xb{bkcbc8bSb^b@bRcPbQb
11 return workspace.root 29b!b%b'b(b)bic}bYcZc+b,b/b:b;b=bjcac0c1c0b1b4b5b6b7bkcbc2c3c
12 node_workspace = find_node_workspace(cwd) 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| } ~ abCcDcTbb Yb|b*bUb_b[bPcbbcbdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJbKbLbMbEcFcVbc Zb~b?bWb`b]bQcNbObd e f g 2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ K L M lcmcRba Xb{b8bSb^b@bRcPbQb
13 if node_workspace is not None: 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| } ~ abCcDcTbb Yb|b*bUb_b[bPcbbcbdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJbKbLbMbEcFcVbc Zb~b?bWb`b]bQcNbObd e f g 2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ K L M lcmcRba Xb{b8bSb^b@bRcPbQb
14 return node_workspace.root 2N O P Q R PcccdcS T U V W QcecfcX Y Z 0 1 Rcgchc
15 for directory in [cwd, *cwd.parents]: 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { $b| } ~ abCcDcTbb Yb|b*bUb_b[bbb)ccb*cdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIb.bJbKbLbMbEcFcVbc Zb~b?bWb`b]bNb+cOb,cd e f g 2bh i j k l m n o p q r s t u v w x y z A B C D E F G H I 3bJ K L M lcmcRba Xb{b8bSb^b@bPb-cQb.c
16 if ( 2d e f g 2bh i j k l m n o p q r s t u v w x y z A B C D E F G H I 3bJ K L M lcmcRba Xb{b8bSb^b@b
17 (directory / "pyproject.toml").is_file()
18 or (directory / "uv.lock").is_file()
19 or _venv_from_dot_venv(directory) is not None
20 or (directory / "package.json").is_file()
21 or (directory / "node_modules").is_dir()
22 ):
23 return directory 22 3 4 5 6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abCcDc|b*bUb[bbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJbKbLbMbEcFc~b?bWb]bNbObd e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M lcmc{b8bSb@bPbQb
24 return None 2#b$bTbb Yb_b)c*c-b.bVbc Zb`b+c,c2b3bRba Xb^b-c.c
27def find_venv(cwd: Path | None = None) -> Path | None: 2tcucvcwcxcyczcAcBc
28 """Find the target Python environment.
30 The CLI can run from an isolated tool environment (for example via uvx), so
31 prefer project-local environment signals before accepting an active shell env.
33 Resolution order:
34 1. UV_PROJECT_ENVIRONMENT env var
35 2. Nearest project .venv
36 3. VIRTUAL_ENV if it appears to belong to the project/user cwd
37 4. CONDA_PREFIX env var
38 """
39 cwd = cwd or Path.cwd() 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abTbb Yb|bic}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbVbc Zb~bjcac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M Rba Xb{bkcbc8bSb^b@bPbQb
40 workspace = find_uv_workspace(cwd) 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abTbb Yb|bic}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbVbc Zb~bjcac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M Rba Xb{bkcbc8bSb^b@bPbQb
41 project_root = find_project_root(cwd) or cwd 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abTbb Yb|bic}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbVbc Zb~bjcac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M Rba Xb{bkcbc8bSb^b@bPbQb
43 # 1. UV_PROJECT_ENVIRONMENT
44 uv_proj_env = os.environ.get("UV_PROJECT_ENVIRONMENT") 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abTbb Yb|bic}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbVbc Zb~bjcac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M Rba Xb{bkcbc8bSb^b@bPbQb
45 if uv_proj_env: 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abTbb Yb|bic}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbVbc Zb~bjcac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M Rba Xb{bkcbc8bSb^b@bPbQb
46 path = Path(uv_proj_env) 2|bicScTc~bjcUcVc{bkcWcXc
47 if not path.is_absolute(): 2|bicScTc~bjcUcVc{bkcWcXc
48 path = project_root / path 2|bicScTc~bjcUcVc{bkcWcXc
49 if (path / "pyvenv.cfg").is_file(): 2|bicScTc~bjcUcVc{bkcWcXc
50 return path 2|bicScTc~bjcUcVc{bkcWcXc
52 # 2. Nearest project .venv directory or PEP 832 redirect file
53 if workspace is not None: 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abTbb Yb}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbVbc Zbac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M Rba Xbbc8bSb^b@bPbQb
54 venv = _venv_from_dot_venv(workspace.root) 29b!b%b'b(b)b}bYcZc+b,b/b:b;b=bac0c1c0b1b4b5b6b7bbc2c3c
55 if venv is not None: 29b!b%b'b(b)b}bYcZc+b,b/b:b;b=bac0c1c0b1b4b5b6b7bbc2c3c
56 return venv 29b!b%b'b(b)bYcZc+b,b/b:b;b=b0c1c0b1b4b5b6b7b2c3c
57 return None 2}bjdkdacldmdbcndod
59 for directory in [cwd, *cwd.parents]: 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| } ~ abTbb Yb*bUb_b[bccbbdccbdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJbKbLbMbVbc Zb?bWb`b]becNbfcObd e f g 2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ K L M Rba Xb8bSb^b@bgcPbhcQb
60 venv = _venv_from_dot_venv(directory) 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| } ~ abTbb Yb*bUb_b[bbbcbdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJbKbLbMbVbc Zb?bWb`b]bNbObd e f g 2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ K L M Rba Xb8bSb^b@bPbQb
61 if venv is not None: 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| } ~ abTbb Yb*bUb_b[bbbcbdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJbKbLbMbVbc Zb?bWb`b]bNbObd e f g 2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ K L M Rba Xb8bSb^b@bPbQb
62 return venv 22 3 4 5 6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ ab*bUbbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJbKbLbMb?bWbNbObd e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M 8bSbPbQb
64 # 3. VIRTUAL_ENV, but avoid uvx/tool environments outside the project tree
65 virtual_env = os.environ.get("VIRTUAL_ENV") 2#bN O P Q R $bTbb Yb_b[bccdc-bS T U V W .bVbc Zb`b]becfc2bX Y Z 0 1 3bRba Xb^b@bgchc
66 if virtual_env: 2#bN O P Q R $bTbb Yb_b[bccdc-bS T U V W .bVbc Zb`b]becfc2bX Y Z 0 1 3bRba Xb^b@bgchc
67 path = Path(virtual_env) 2N O P Q R Yb[bccdcS T U V W Zb]becfcX Y Z 0 1 Xb@bgchc
68 if (path / "pyvenv.cfg").is_file() and _is_relative_to(path, project_root): 2N O P Q R Yb[bccdcS T U V W Zb]becfcX Y Z 0 1 Xb@bgchc
69 return path 2[b/c:c]b;c=c@b?c@c
71 # 4. CONDA_PREFIX
72 conda_prefix = os.environ.get("CONDA_PREFIX") 2#bN O P Q R $bTbb Yb_bccdc-bS T U V W .bVbc Zb`becfc2bX Y Z 0 1 3bRba Xb^bgchc
73 if conda_prefix: 2#bN O P Q R $bTbb Yb_bccdc-bS T U V W .bVbc Zb`becfc2bX Y Z 0 1 3bRba Xb^bgchc
74 path = Path(conda_prefix) 2_b4c5c`b6c7c^b8c9c
75 if path.is_dir(): 2_b4c5c`b6c7c^b8c9c
76 return path 2_b4c5c`b6c7c^b8c9c
78 return None 2#bN O P Q R $bTbb Ybccdc-bS T U V W .bVbc Zbecfc2bX Y Z 0 1 3bRba Xbgchc
81def get_site_packages_dir(venv_path: Path) -> Path | None: 2tcucvcwcxcyczcAcBc
82 """Get the site-packages directory for a Python environment."""
83 windows_site_packages = venv_path / "Lib" / "site-packages" 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPbQb
84 if windows_site_packages.is_dir(): 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPbQb
85 return windows_site_packages 2Hc[c]cIc^c_cGc`c{c
87 # Unix: lib/pythonX.Y/site-packages or lib64/pythonX.Y/site-packages
88 for lib_name in ("lib", "lib64"): 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbb[ccb]cdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNb^cOb_cd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPb`cQb{c
89 lib_dir = venv_path / lib_name 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPbQb
90 if lib_dir.is_dir(): 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPbQb
91 for child in sorted(lib_dir.iterdir(), reverse=True): 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbb|ccb}cdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNb~cObadd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPbbdQbcd
92 site_packages = child / "site-packages" 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abKcHcbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbLcIcNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGcPbQb
93 if ( 2d e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M JcGc
94 child.name.startswith("python")
95 and child.is_dir()
96 and site_packages.is_dir()
97 ):
98 return site_packages 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abHcbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbIcNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M GcPbQb
100 return None 2Kc|c}cLc~cadJcbdcd
103def find_node_modules(cwd: Path | None = None) -> Path | None: 2tcucvcwcxcyczcAcBc
104 """Find the nearest Node.js node_modules directory walking up from cwd."""
105 cwd = cwd or Path.cwd() 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abMcbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbNcNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M OcPbQb
106 for directory in [cwd, *cwd.parents]: 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abMcbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbNcNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M OcPbQb
107 node_modules = directory / "node_modules" 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abMcbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbNcNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M OcPbQb
108 if node_modules.is_dir(): 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abMcbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbNcNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M OcPbQb
109 return node_modules 29b!bN O P Q R { Mcpdqd+b,bS T U V W IbNcrdsd0b1bX Y Z 0 1 I Octdud
110 return None 22 3 4 5 #b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` $b| %b'b(b)b} ~ abMcbbcbdbebfbgb-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHb.bJb/b:b;b=bKbLbMbNcNbObd e f g 2bh i j k l m n o p q r s t u v w x y z A B C D E F G H 3bJ 4b5b6b7bK L M OcPbQb
113def _is_relative_to(path: Path, parent: Path) -> bool: 2tcucvcwcxcyczcAcBc
114 try: 2N O P Q R Yb[bccdcS T U V W Zb]becfcX Y Z 0 1 Xb@bgchc
115 path.resolve().relative_to(parent.resolve()) 2N O P Q R Yb[bccdcS T U V W Zb]becfcX Y Z 0 1 Xb@bgchc
116 except ValueError: 2N O P Q R YbccdcS T U V W ZbecfcX Y Z 0 1 Xbgchc
117 return False 2N O P Q R YbccdcS T U V W ZbecfcX Y Z 0 1 Xbgchc
118 return True 2[b/c:c]b;c=c@b?c@c
121def _venv_from_dot_venv(project_root: Path) -> Path | None: 2tcucvcwcxcyczcAcBc
122 dot_venv = project_root / ".venv" 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abCcDcTbb Yb}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbEcFcVbc Zbac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M lcmcRba Xbbc8bSb^b@bPbQb
123 if dot_venv.is_dir(): 22 3 4 5 9b!b#b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? N O P Q R @ [ ] ^ _ ` { $b| %b'b(b)b} ~ abCcDcTbb Yb}b*bUb_b[bbbcbdbebfbgb+b,b-bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbS T U V W CbDbEbFbGbHbIb.bJb/b:b;b=bKbLbMbEcFcVbc Zbac?bWb`b]bNbObd e f g 0b1b2bh i j k l m n o p q r s t u v w x y z A B X Y Z 0 1 C D E F G H I 3bJ 4b5b6b7bK L M lcmcRba Xbbc8bSb^b@bPbQb
124 if (dot_venv / "pyvenv.cfg").is_file(): 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abTbbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbVbNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M RbPbQb
125 return dot_venv 22 3 4 5 9b!b6 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | %b'b(b)b} ~ abbbcbdbebfbgb+b,bhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbAbBbCbDbEbFbGbHbIbJb/b:b;b=bKbLbMbNbObd e f g 0b1bh i j k l m n o p q r s t u v w x y z A B C D E F G H I J 4b5b6b7bK L M PbQb
126 return None 2Tb!c#cVb$c%cRb'c(c
127 if dot_venv.is_file(): 2#bN O P Q R $bCcDcTbb Yb}b*bUb_b[bccdc-bS T U V W .bEcFcVbc Zbac?bWb`b]becfc2bX Y Z 0 1 3blcmcRba Xbbc8bSb^b@bgchc
128 return _read_venv_redirect_file(dot_venv) 2Tbb *bUbncocVbc ?bWbpcqcRba 8bSbrcsc
129 return None 2#bN O P Q R $bCcDcTbb Yb}bUb_b[bccdc-bS T U V W .bEcFcVbc ZbacWb`b]becfc2bX Y Z 0 1 3blcmcRba XbbcSb^b@bgchc
132def _read_venv_redirect_file(path: Path) -> Path | None: 2tcucvcwcxcyczcAcBc
133 try: 2Tbb *bUbncocVbc ?bWbpcqcRba 8bSbrcsc
134 content = path.read_text(encoding="utf-8") 2Tbb *bUbncocVbc ?bWbpcqcRba 8bSbrcsc
135 except OSError: 2Tb!c#cVb$c%cRb'c(c
136 return None 2Tb!c#cVb$c%cRb'c(c
137 redirect = content.splitlines()[0] if content.splitlines() else "" 2b *bUbncocc ?bWbpcqca 8bSbrcsc
138 if not redirect: 2b *bUbncocc ?bWbpcqca 8bSbrcsc
139 return None 2b ddedc fdgda hdid
140 venv = Path(redirect) 2b *bUbncocc ?bWbpcqca 8bSbrcsc
141 if not venv.is_absolute(): 2b *bUbncocc ?bWbpcqca 8bSbrcsc
142 venv = path.parent / venv 2b Ubncocc Wbpcqca Sbrcsc
143 venv = venv.resolve() 2b *bUbncocc ?bWbpcqca 8bSbrcsc
144 if (venv / "pyvenv.cfg").is_file(): 2b *bUbncocc ?bWbpcqca 8bSbrcsc
145 return venv 2*bUbncoc?bWbpcqc8bSbrcsc
146 return None 2b ddedc fdgda hdid