Skip to content

Commit

Permalink
Add a public API via the create() function (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
brettcannon authored Mar 10, 2023
1 parent 73690b1 commit a072ad5
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 45 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ In general, though, using the [`venv` module](https://docs.python.org/3/library/
## Usage

```console
python microvenv.py [path=".venv"]
python microvenv.py [env_dir=".venv"]
```

If an argument is provided to the script, it is used as the path to create the virtual environment in. Otherwise, the virtual environment is created in `.venv`.

For programmatic usage, use the [`runpy` module](https://docs.python.org/3/library/runpy.html#module-runpy) to execute the script:
For programmatic usage, there is the `create()` function, which is analogous to the [`venv.create()` function](https://docs.python.org/3/library/venv.html#venv.create).

```python
runpy.run_path("microvenv.py", run_name="__main__")
def create(env_dir: os.PathLike = ".venv") -> None
```

The contents of `microvenv.py` is also small enough to be passed in via the `-c` flag to `python`.
The `microvenv.py` file is also small enough to have its contents passed in via the `-c` flag to `python`.

## Differences compared to the [`venv` module](https://docs.python.org/3/library/venv.html#module-venv)

The module operates similarly to `py -m venv --symlinks --without-pip .venv`,
except that:

- There are no activation scripts (execute `python` in the virtual environment directly).
- Windows is not supported.
- There are no activation scripts (you can execute `python` in the virtual environment directly)
- Windows is not supported
59 changes: 29 additions & 30 deletions microvenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,40 @@
import sys
import sysconfig

PYVENVCFG_TEMPLATE = f"""home = {{base_executable_parent}}
_BASE_EXECUTABLE = pathlib.Path(getattr(sys, "_base_executable", sys.executable))

_PYVENVCFG_TEMPLATE = f"""home = {_BASE_EXECUTABLE.parent}
include-system-site-packages = false
version = {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}
executable = {{resolved_base_executable}}
version = {'.'.join(map(str, sys.version_info[:3]))}
executable = {_BASE_EXECUTABLE.resolve()}
command = {{command}}
"""


def _sysconfig_path(name, venv_dir):
def _sysconfig_path(name, env_dir):
variables = {
"base": venv_dir,
"platbase": venv_dir,
"installed_base": venv_dir,
"installed_platbase": venv_dir,
"base": env_dir,
"platbase": env_dir,
"installed_base": env_dir,
"installed_platbase": env_dir,
}

return pathlib.Path(sysconfig.get_path(name, "venv", variables))


def create(venv_dir):
base_executable = pathlib.Path(getattr(sys, "_base_executable", sys.executable))

# Analogous to `venv.create()`.
def create(env_dir=".venv"):
"""Create a minimal virtual environment."""
env_path = pathlib.Path(env_dir)
try:
scripts_dir = _sysconfig_path("scripts", venv_dir)
include_dir = _sysconfig_path("include", venv_dir)
purelib_dir = _sysconfig_path("purelib", venv_dir)
scripts_dir = _sysconfig_path("scripts", env_path)
include_dir = _sysconfig_path("include", env_path)
purelib_dir = _sysconfig_path("purelib", env_path)
except KeyError:
scripts_dir = venv_dir / "bin"
include_dir = venv_dir / "include"
scripts_dir = env_path / "bin"
include_dir = env_path / "include"
purelib_dir = (
venv_dir
env_path
/ "lib"
/ f"python{sys.version_info.major}.{sys.version_info.minor}"
/ "site-packages"
Expand All @@ -42,38 +45,34 @@ def create(venv_dir):
dir.mkdir(parents=True)

if sys.maxsize > 2**32 and os.name == "posix" and sys.platform != "darwin":
(venv_dir / "lib64").symlink_to("lib", target_is_directory=True)
(env_path / "lib64").symlink_to("lib", target_is_directory=True)

for executable_name in (
"python",
f"python{sys.version_info.major}",
f"python{sys.version_info.major}.{sys.version_info.minor}",
):
(scripts_dir / executable_name).symlink_to(base_executable)
(scripts_dir / executable_name).symlink_to(_BASE_EXECUTABLE)

try:
module_path = pathlib.Path(__file__).resolve()
except NameError:
command = f"{sys.executable} -c '...'"
else:
command = f"{sys.executable} {module_path} {venv_dir.resolve()}"
(venv_dir / "pyvenv.cfg").write_text(
PYVENVCFG_TEMPLATE.format(
base_executable_parent=base_executable.parent,
resolved_base_executable=base_executable.resolve(),
command=command,
),
command = f"{sys.executable} {module_path} {env_path.resolve()}"
(env_path / "pyvenv.cfg").write_text(
_PYVENVCFG_TEMPLATE.format(command=command),
encoding="utf-8",
)


if __name__ == "__main__":
if len(sys.argv) > 2:
print("Usage: microvenv.py [venv_dir='.venv']", file=sys.stderr)
print("Usage: microvenv.py [env_dir='.venv']", file=sys.stderr)
sys.exit(1)
try:
venv_dir = sys.argv[1]
env_dir = sys.argv[1]
except IndexError:
venv_dir = ".venv"
env_dir = ".venv"

create(pathlib.Path(venv_dir))
create(env_dir)
7 changes: 2 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "microvenv"
version = "2023.0.1"
version = "2023.1.0"
description = "A minimal re-implementation of Python's venv module"
keywords = ["virtual environments", "venv"]
readme = "README.md"
Expand All @@ -15,15 +15,12 @@ requires-python = ">=3.7"
classifiers = [
"Development Status :: 3 - Alpha",
"License :: OSI Approved :: MIT License",
"Environment :: Console",
"Intended Audience :: Developers",
"Topic :: Software Development",
"Topic :: Utilities",
"Natural Language :: English",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Operating System :: POSIX",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
Expand Down
5 changes: 1 addition & 4 deletions test_microvenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ def executable():
@pytest.fixture(scope="session")
def full_venv(tmp_path_factory):
venv_path = tmp_path_factory.mktemp("venvs") / "full_venv"
venv_builder = venv.EnvBuilder(
symlinks=True, with_pip=False, system_site_packages=False
)
venv_builder.create(venv_path)
venv.create(venv_path, symlinks=True, with_pip=False, system_site_packages=False)
return venv_path


Expand Down

0 comments on commit a072ad5

Please sign in to comment.