1
1
from __future__ import annotations
2
2
3
- import functools
4
3
import logging
5
4
import os
6
5
import pathlib
7
6
import sys
8
7
import sysconfig
9
- import tempfile
10
- import typing
11
8
from contextlib import contextmanager
12
9
from typing import TYPE_CHECKING
13
10
14
- from variantlib .plugins .py_backends import AutoInstallBackend
15
- from variantlib .plugins .py_backends import PipBackend
16
- from variantlib .plugins .py_backends import UvBackend
17
-
18
11
if TYPE_CHECKING :
19
- from collections .abc import Collection
20
12
from collections .abc import Generator
21
13
22
14
23
15
logger = logging .getLogger (__name__ )
24
16
25
- Installer = typing .Literal ["pip" , "uv" ]
26
-
27
- INSTALLERS = typing .get_args (Installer )
28
-
29
17
30
18
def _find_executable (path : pathlib .Path ) -> pathlib .Path :
31
19
"""
@@ -78,52 +66,10 @@ def _find_executable(path: pathlib.Path) -> pathlib.Path:
78
66
return executable
79
67
80
68
81
- @functools .cache
82
- def _fs_supports_symlink () -> bool :
83
- """Return True if symlinks are supported"""
84
- # Using definition used by venv.main()
85
- if os .name != "nt" :
86
- return True
87
-
88
- # Windows may support symlinks (setting in Windows 10)
89
- with tempfile .NamedTemporaryFile (prefix = "variant-symlink-" ) as tmp_file :
90
- dest = f"{ tmp_file } -b"
91
- try :
92
- os .symlink (tmp_file .name , dest )
93
- os .unlink (dest ) # noqa: PTH108
94
- except (OSError , NotImplementedError , AttributeError ):
95
- return False
96
- return True
97
-
98
-
99
69
class PythonEnv :
100
- _env_backend : UvBackend | PipBackend = AutoInstallBackend ()
101
-
102
70
def __init__ (self , venv_path : pathlib .Path | None = None ):
103
71
self .venv_path = venv_path
104
72
105
- def install (self , requirements : Collection [str ]) -> None :
106
- """
107
- Install packages from PEP 508 requirements in the isolated variant plugin
108
- environment.
109
-
110
- :param requirements: PEP 508 requirement specification to install
111
-
112
- :note: Passing non-PEP 508 strings will result in undefined behavior, you
113
- *should not* rely on it. It is merely an implementation detail, it may
114
- change any time without warning.
115
- """
116
- if not requirements :
117
- return
118
-
119
- logger .info (
120
- "Installing packages in current environment:\n %(reqs)s" ,
121
- {"reqs" : "\n " .join (f"- { r } " for r in sorted (requirements ))},
122
- )
123
- self ._env_backend .install_requirements (
124
- requirements , py_exec = self .python_executable
125
- )
126
-
127
73
@property
128
74
def python_executable (self ) -> pathlib .Path :
129
75
if self .venv_path is not None :
@@ -134,23 +80,5 @@ def python_executable(self) -> pathlib.Path:
134
80
135
81
136
82
@contextmanager
137
- def python_env (
138
- isolated : bool = False , venv_path : pathlib .Path | None = None
139
- ) -> Generator [PythonEnv ]:
140
- if venv_path is None and isolated :
141
- import venv
142
-
143
- with tempfile .TemporaryDirectory (prefix = "variantlib-venv" ) as temp_dir :
144
- logger .info (
145
- "Creating virtual environment in %(path)s ..." ,
146
- {"path" : str (temp_dir )},
147
- )
148
- venv .EnvBuilder (
149
- symlinks = _fs_supports_symlink (),
150
- with_pip = True ,
151
- system_site_packages = False ,
152
- clear = True ,
153
- ).create (temp_dir )
154
- yield PythonEnv (pathlib .Path (temp_dir ))
155
- else :
156
- yield PythonEnv (venv_path )
83
+ def python_env (venv_path : pathlib .Path | None = None ) -> Generator [PythonEnv ]:
84
+ yield PythonEnv (venv_path )
0 commit comments