Skip to content

Commit

Permalink
Merge pull request #29 from kevinconway/provisional_venv_support
Browse files Browse the repository at this point in the history
Add provisional stdlib venv support
  • Loading branch information
kevinconway authored Jun 14, 2024
2 parents e3167ac + 2d2a275 commit c1dc019
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 24 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Python 3.7
- name: Setup Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: 3.8
- name: Cache PyPI
uses: actions/cache@v2
with:
Expand All @@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
pyver: ["3.7", "3.8", "3.9", "3.10", "3.11"]
pyver: ["3.8", "3.9", "3.10", "3.11", "3.12"]
fail-fast: true
steps:
- name: Install deps
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

setup(
name="venvctrl",
version="0.6.0",
version="0.7.0",
url="https://github.com/kevinconway/venvctrl",
description="API for virtual environments.",
author="Kevin Conway",
Expand Down
35 changes: 35 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Test fixtures and configuration."""

from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import uuid

import pytest

from venvctrl import api


def pytest_generate_tests(metafunc):
if "use_stdlib_venv" in metafunc.fixturenames:
metafunc.parametrize("use_stdlib_venv", (True, False))


@pytest.fixture(scope="function")
def random():
"""Get a random UUID."""
return str(uuid.uuid4())


@pytest.fixture(scope="function")
def venv(random, tmpdir, use_stdlib_venv):
"""Get an initialized venv."""
path = str(tmpdir.join(random))
v = api.VirtualEnvironment(path)
if not use_stdlib_venv:
v.create()
else:
v._execute("python -m venv {0}".format(path))
return v
17 changes: 0 additions & 17 deletions tests/test_virtual_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,10 @@

import os
import subprocess
import uuid

import pytest

from venvctrl import api


@pytest.fixture(scope="function")
def random():
"""Get a random UUID."""
return str(uuid.uuid4())


@pytest.fixture(scope="function")
def venv(random, tmpdir):
"""Get an initialized venv."""
v = api.VirtualEnvironment(str(tmpdir.join(random)))
v.create()
return v


def test_create(random, tmpdir):
"""Test if new virtual environments can be created."""
path = str(tmpdir.join(random))
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py37,py38,py39,py310,pyt311,pep8,pyflakes
envlist = py38,py39,py310,py311,py312,pep8,pyflakes

[testenv]
deps=
Expand Down
88 changes: 86 additions & 2 deletions venvctrl/venv/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ def writeline(self, line, line_number):

tmp_file.close()

def replace(self, old, new):
"""Replace old with new in every occurrence.
Args:
old (str): The original text.
new (str): The new text.
"""
tmp_file = tempfile.TemporaryFile("w+")
try:

with open(self.path, "r") as file_handle:

for line in file_handle:

line = line.replace(old, new)
tmp_file.write(line)

tmp_file.seek(0)
with open(self.path, "w") as file_handle:

for new_line in tmp_file:

file_handle.write(new_line)
finally:

tmp_file.close()


class VenvDir(VenvPath):

Expand Down Expand Up @@ -205,7 +232,12 @@ def shebang(self, new_shebang):

class ActivateFile(BinFile):

"""The virtual environment /bin/activate script."""
"""A common base for all activate scripts.
Implementations should replace the read_pattern for cases where the path can
be extracted with a regex. More complex use cases should override the
_find_vpath method to perform a search and return the appropriate path.
"""

read_pattern = re.compile(r"""^VIRTUAL_ENV=["'](.*)["']$""")

Expand Down Expand Up @@ -241,6 +273,58 @@ def vpath(self, new_vpath):
self.writeline(new_line, line_number)


class ActivateFileBash(ActivateFile):

"""The virtual environment /bin/activate script.
This version accounts for differences between the virtualenv and venv
activation scripts for bash.
"""

read_pattern = re.compile(r"""^VIRTUAL_ENV=["'](.*)["']$""")
read_pattern_stdlib_venv = re.compile(r"""^ *export VIRTUAL_ENV=["'](.*)["']$""")

def _find_vpath(self):
"""
Find the VIRTUAL_ENV path entry.
Returns:
tuple: A tuple containing the matched line, the old vpath, and the line number where the virtual
path was found. If the virtual path is not found, returns a tuple of three None values.
"""
with open(self.path, "r") as file_handle:

for count, line in enumerate(file_handle):

match = self.read_pattern.match(line)
if match:

return match.group(0), match.group(1), count
match = self.read_pattern_stdlib_venv.match(line)
if match:

return match.group(0), match.group(1), count

return None, None, None

@property
def vpath(self):
"""Get the path to the virtual environment."""
return self._find_vpath()[1]

@vpath.setter
def vpath(self, new_vpath):
"""Change the path to the virtual environment.
The bash activate file from the standard library venv duplicates the
full path in multiple places instead of only one place like in
virtualenv. To account, this code now does a line by line replacement
of the old path to ensure that it is replaced everywhere.
"""
_, old_vpath, _ = self._find_vpath()
self.replace(old_vpath, new_vpath)


class ActivateFishFile(ActivateFile):

"""The virtual environment /bin/activate.fish script."""
Expand Down Expand Up @@ -338,7 +422,7 @@ def activates(self):
@property
def activate_sh(self):
"""Get the /bin/activate script."""
return ActivateFile(os.path.join(self.path, "activate"))
return ActivateFileBash(os.path.join(self.path, "activate"))

@property
def activate_csh(self):
Expand Down

0 comments on commit c1dc019

Please sign in to comment.