diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..750464f --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +show-source = True +max-line-length = 80 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5737055 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "daily" diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 0000000..db9e8a2 --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,81 @@ +name: Build and test + +on: + # Only on pushes to master or one of the release branches we build on push + push: + branches: + - master + - "[0-9].[0-9]+-branch" + tags: + - "*" + # Build pull requests + pull_request: + +jobs: + test: + strategy: + matrix: + py: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "pypy-3.8" + os: + - "ubuntu-latest" + - "windows-2022" + - "macos-12" + architecture: + - x64 + - x86 + + exclude: + # Linux and macOS don't have x86 python + - os: "ubuntu-latest" + architecture: x86 + - os: "macos-12" + architecture: x86 + + name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.py }} + architecture: ${{ matrix.architecture }} + - run: pip install tox + - name: Running tox + run: tox -e py + coverage: + runs-on: ubuntu-latest + name: Validate coverage + steps: + - uses: actions/checkout@v3 + - name: Setup python 3.7 + uses: actions/setup-python@v4 + with: + python-version: 3.7 + architecture: x64 + - name: Setup python 3.12 + uses: actions/setup-python@v4 + with: + python-version: 3.12 + architecture: x64 + - run: pip install tox + - run: tox -e py37,py312,coverage + lint: + runs-on: ubuntu-latest + name: Lint the package + steps: + - uses: actions/checkout@v3 + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + architecture: x64 + - run: pip install tox + - run: tox -e lint diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 21ff441..0000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -sudo: false - -cache: - directories: - - $HOME/.cache/pip - -language: python - -matrix: - include: - - python: '2.7' - env: TOXENV=py27 - - python: '3.4' - env: TOXENV=py34 - - python: '3.5' - env: TOXENV=py35 - - python: '3.6' - env: TOXENV=py36 - - python: '3.7' - env: TOXENV=py37 - - python: '3.8' - env: TOXENV=py38,coverage,pep8 - - python: '3.9-dev' - env: TOXENV=py39 - - python: 'pypy' - env: TOXENV=pypy - - python: 'pypy3' - env: TOXENV=pypy3 - -install: pip install tox - -script: tox diff --git a/CHANGES.rst b/CHANGES.rst index aeecd76..c0da877 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,21 @@ +0.3 (2023-11-22) +================ + +- Drop Python 2.7, 3.4, 3.5, 3.6. + +- Add support for Python 3.9, 3.10, 3.11, 3.12. + +- No longer expect ``pkg_resources`` to be available in the created virtualenv. + +- No longer depend on ``setuptools``. + +- Add ``extra_args`` to ``install()`` and ``create()`` to pass extra arguments + to the underlying commands. + +- Add ``raises=False`` option to ``get_version()`` to avoid raising an + exception if a package is not installed. + + 0.2.1 (2020-08-04) ================== diff --git a/MANIFEST.in b/MANIFEST.in index 6c31235..ca715a8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,7 +7,8 @@ include CHANGES.rst include LICENSE.txt include CONTRIBUTING.rst -include .coveragerc -include tox.ini appveyor.yml .travis.yml rtd.txt +include pyproject.toml +include .coveragerc .flake8 pytest.ini +include tox.ini recursive-exclude * __pycache__ *.py[cod] diff --git a/README.rst b/README.rst index 159b68a..a65ea67 100644 --- a/README.rst +++ b/README.rst @@ -56,5 +56,5 @@ The ``venv`` fixture is an instance of ``get_version(pkg_name)`` - Returns a ``pkg_resources.Version`` object which is sortable and convertable - to a string. + Returns a ``packaging.version.Version`` object which is sortable and + convertable to a string. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index fb90d4d..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -environment: - matrix: - - PYTHON: "C:\\Python35" - TOXENV: "py35" - - PYTHON: "C:\\Python27" - TOXENV: "py27" - - PYTHON: "C:\\Python35-x64" - TOXENV: "py35" - - PYTHON: "C:\\Python27-x64" - TOXENV: "py27" - -cache: - - '%LOCALAPPDATA%\pip\Cache' - -version: '{branch}.{build}' - -install: - - "%PYTHON%\\python.exe -m pip install tox" - -build: off - -test_script: - - "%PYTHON%\\Scripts\\tox.exe" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..59f928d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools>=41.0.1", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 79 +skip-string-normalization = true +target_version = ["py37", "py38", "py39", "py310", "py311", "py312"] +exclude = ''' +/( + \.git + | \.mypy_cache + | \.tox + | \.venv + | \.pytest_cache + | dist + | build + | docs +)/ +''' + +# This next section only exists for people that have their editors +# automatically call isort, black already sorts entries on its own when run. +[tool.isort] +profile = "black" +py_version = 3 +combine_as_imports = true +line_length = 79 +force_sort_within_sections = true +no_lines_before = "THIRDPARTY" +sections = "FUTURE,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" +default_section = "THIRDPARTY" +known_first_party = "pytest_venv" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..557dc4d --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +python_files = test_*.py +testpaths = + src/pytest_venv + tests diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9a3ed4a..0000000 --- a/setup.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[wheel] -universal = 1 - -[flake8] -show-source = True -max-line-length = 80 - -[tool:pytest] -python_files = test_*.py -testpaths = - src/pytest_venv - tests diff --git a/setup.py b/setup.py index 35f6c3e..8ca88db 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,17 @@ -from setuptools import setup, find_packages +from setuptools import find_packages, setup + def readfile(name): with open(name) as f: return f.read() + readme = readfile('README.rst') changes = readfile('CHANGES.rst') requires = [ + 'packaging', 'pytest', - 'setuptools', 'virtualenv', ] @@ -19,9 +21,10 @@ def readfile(name): setup( name='pytest-venv', - version='0.2.1', + version='0.3', description='py.test fixture for creating a virtual environment', long_description=readme + '\n\n' + changes, + long_description_content_type='text/x-rst', author='Michael Merickel', author_email='michael@merickel.org', url='https://github.com/mmerickel/pytest-venv', @@ -40,14 +43,13 @@ def readfile(name): 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Testing', diff --git a/src/pytest_venv/__init__.py b/src/pytest_venv/__init__.py index f1d5121..7260672 100644 --- a/src/pytest_venv/__init__.py +++ b/src/pytest_venv/__init__.py @@ -1,8 +1,9 @@ import os -import pkg_resources +import packaging.version as pv import pytest -import sys import subprocess +import sys +import textwrap WIN = sys.platform == 'win32' @@ -23,27 +24,47 @@ def __init__(self, path): ) self.python = os.path.join(self.bin, 'python') - def create(self, system_packages=False, python=None): + def create(self, system_packages=False, python=None, *, extra_args=None): cmd = [sys.executable, '-m', 'virtualenv'] cmd += ['-p', python or sys.executable] if system_packages: cmd += ['--system-site-packages'] + if extra_args: + cmd += extra_args cmd += [self.path] subprocess.check_call(cmd) - def install(self, pkg_name, editable=False, upgrade=False): + def install( + self, pkg_name, editable=False, upgrade=False, *, extra_args=None + ): cmd = [self.python, '-m', 'pip', 'install'] if upgrade: cmd += ['-U'] + if extra_args: + cmd += extra_args if editable: cmd += ['-e'] cmd += [pkg_name] subprocess.check_call(cmd) - def get_version(self, pkg_name): - script = ( - 'import pkg_resources; ' - 'print(pkg_resources.get_distribution("%(pkg_name)s").version)' - ) % dict(pkg_name=pkg_name) + def get_version(self, pkg_name, *, raises=True): + script = textwrap.dedent( + f''' + try: + from importlib.metadata import version + except ImportError: + import pkg_resources + version = lambda x: pkg_resources.get_distribution(x).version + + try: + print(version("{pkg_name}")) + except Exception: + print('') + ''' + ) version = subprocess.check_output([self.python, '-c', script]).strip() - return pkg_resources.parse_version(version.decode('utf8')) + if not version: + if raises: + raise Exception('package is not installed') + return None + return pv.Version(version.decode('utf8')) diff --git a/tests/test_it.py b/tests/test_it.py index 72d191f..dbb0f8e 100644 --- a/tests/test_it.py +++ b/tests/test_it.py @@ -1,6 +1,7 @@ import os -import sys +import pytest import subprocess +import sys here = os.path.abspath(os.path.dirname(__file__)) @@ -13,8 +14,26 @@ def test_it(venv): def test_it_installs_dep(venv): - venv.install('pyramid') - subprocess.check_call([venv.python, '-c', 'import pyramid']) + venv.install('webob') + subprocess.check_call([venv.python, '-c', 'import webob']) + + +def test_it_installs_dep_with_extra_args(venv): + venv.install('webob', extra_args=['--prefer-binary']) + subprocess.check_call([venv.python, '-c', 'import webob']) + + +@pytest.mark.skipif( + sys.version_info < (3, 12), reason="Make sense only for Python 3.12" +) +def test_it_installs_dep_without_setuptools(venv): + # micropipenv does not depend on setuptools + # so this test verifies that `get_version` works + # fine even when setuptools/pkg_resources are + # not available in the virtual environment. + venv.install('micropipenv') + subprocess.check_call([venv.python, '-c', 'import micropipenv']) + venv.get_version('micropipenv') def test_it_installs_editable_dep(venv): @@ -32,18 +51,21 @@ def test_it_upgrades_dep(venv): assert version2 > version1 -def test_it_uses_correct_python(venv): - result = subprocess.check_output( - [venv.python, '-c', 'import sys; print(sys.version)'], - ) - assert result.decode('utf8').strip() == str(sys.version) +def test_get_version_returns_none(venv): + version = venv.get_version('pyramid', raises=False) + assert version is None + + +def test_get_version_raises(venv): + with pytest.raises(Exception, match='package is not installed'): + venv.get_version('pyramid') def test_it_creates_with_system_packages(tmpdir): from pytest_venv import VirtualEnvironment venv = VirtualEnvironment(tmpdir.strpath) - venv.create(system_packages=True) + venv.create(system_packages=True, extra_args=['--no-setuptools']) result = subprocess.check_output( [venv.python, '-c', 'print("hello world")'], diff --git a/tox.ini b/tox.ini index 21c03ba..d3e2430 100644 --- a/tox.ini +++ b/tox.ini @@ -1,38 +1,66 @@ [tox] envlist = - pep8, - py27,py34,py35,py36,py37,py38,py39,pypy,pypy3 + lint, + py37,py38,py39,py310,py311,py312,pypy3 coverage [testenv] -basepython = - py27: python2.7 - py34: python3.4 - py35: python3.5 - py36: python3.6 - py37: python3.7 - py38: python3.8 - py39: python3.9 - pypy: pypy - pypy3: pypy3 - py2: python2.7 - py3: python3.8 - commands = - pip install pytest-venv[testing] coverage run -m pytest {posargs:} +extras = + testing + [testenv:coverage] -basepython = python3.8 commands = coverage combine coverage report --fail-under=100 deps = coverage -[testenv:pep8] -basepython = python3.8 +[testenv:lint] +skip_install = True commands = - flake8 src/pytest_venv/ tests/ + isort --check-only --df src/pytest_venv tests setup.py + black --check --diff src/pytest_venv tests setup.py + flake8 src/pytest_venv tests setup.py + check-manifest + # build sdist/wheel + python -m build . + twine check dist/* deps = + black + build + check-manifest flake8 + flake8-bugbear + isort + readme_renderer + twine + +[testenv:format] +skip_install = true +commands = + isort src/pytest_venv tests setup.py + black src/pytest_venv tests setup.py +deps = + black + isort + +[testenv:build] +skip_install = true +commands = + # clean up build/ and dist/ folders + python -c 'import shutil; shutil.rmtree("build", ignore_errors=True)' + # Make sure we aren't forgetting anything + check-manifest + # build sdist/wheel + python -m build . + # Verify all is well + twine check dist/* + +deps = + build + check-manifest + readme_renderer + twine