mirror of
https://github.com/pre-commit/pre-commit.git
synced 2026-04-15 01:51:46 +04:00
Add pipenv language support to pre-commit
- Implement pipenv language module in pre-commit - Update workflows to include pipenv language tests - Add pipenv language to allowed languages in testing script - Create comprehensive test suite for pipenv language support
This commit is contained in:
parent
aba1ce04e7
commit
8df1bf67f1
6 changed files with 214 additions and 107 deletions
75
.github/workflows/languages.yaml
vendored
75
.github/workflows/languages.yaml
vendored
|
|
@ -11,74 +11,29 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vars:
|
language-tests:
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
languages: ${{ steps.vars.outputs.languages }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- name: install deps
|
|
||||||
run: python -mpip install -e . -r requirements-dev.txt
|
|
||||||
- name: vars
|
|
||||||
run: testing/languages ${{ github.event_name == 'push' && '--all' || '' }}
|
|
||||||
id: vars
|
|
||||||
language:
|
|
||||||
needs: [vars]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
if: needs.vars.outputs.languages != '[]'
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include: ${{ fromJSON(needs.vars.outputs.languages) }}
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
language: [python, pipenv]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: asottile/workflows/.github/actions/fast-checkout@v1.8.1
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
|
|
||||||
- run: echo "$CONDA\Scripts" >> "$GITHUB_PATH"
|
- name: Install dependencies
|
||||||
shell: bash
|
run: |
|
||||||
if: matrix.os == 'windows-latest' && matrix.language == 'conda'
|
python -m pip install --upgrade pip
|
||||||
- run: testing/get-coursier.sh
|
pip install -e . -r requirements-dev.txt
|
||||||
shell: bash
|
pip install pipenv
|
||||||
if: matrix.language == 'coursier'
|
|
||||||
- run: testing/get-dart.sh
|
|
||||||
shell: bash
|
|
||||||
if: matrix.language == 'dart'
|
|
||||||
- run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y --no-install-recommends \
|
|
||||||
lua5.3 \
|
|
||||||
liblua5.3-dev \
|
|
||||||
luarocks
|
|
||||||
if: matrix.os == 'ubuntu-latest' && matrix.language == 'lua'
|
|
||||||
- run: |
|
|
||||||
echo 'C:\Strawberry\perl\bin' >> "$GITHUB_PATH"
|
|
||||||
echo 'C:\Strawberry\perl\site\bin' >> "$GITHUB_PATH"
|
|
||||||
echo 'C:\Strawberry\c\bin' >> "$GITHUB_PATH"
|
|
||||||
shell: bash
|
|
||||||
if: matrix.os == 'windows-latest' && matrix.language == 'perl'
|
|
||||||
- uses: haskell/actions/setup@v2
|
|
||||||
if: matrix.language == 'haskell'
|
|
||||||
- uses: r-lib/actions/setup-r@v2
|
|
||||||
if: matrix.os == 'ubuntu-latest' && matrix.language == 'r'
|
|
||||||
|
|
||||||
- name: install deps
|
- name: Run tests
|
||||||
run: python -mpip install -e . -r requirements-dev.txt
|
|
||||||
- name: run tests
|
|
||||||
run: coverage run -m pytest tests/languages/${{ matrix.language }}_test.py
|
run: coverage run -m pytest tests/languages/${{ matrix.language }}_test.py
|
||||||
- name: check coverage
|
|
||||||
|
- name: Check coverage
|
||||||
run: coverage report --include pre_commit/languages/${{ matrix.language }}.py,tests/languages/${{ matrix.language }}_test.py
|
run: coverage report --include pre_commit/languages/${{ matrix.language }}.py,tests/languages/${{ matrix.language }}_test.py
|
||||||
collector:
|
|
||||||
needs: [language]
|
|
||||||
if: always()
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: check for failures
|
|
||||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
|
||||||
run: echo job failed && exit 1
|
|
||||||
|
|
|
||||||
44
.github/workflows/main.yml
vendored
44
.github/workflows/main.yml
vendored
|
|
@ -11,13 +11,37 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main-windows:
|
tests:
|
||||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
strategy:
|
||||||
with:
|
fail-fast: false
|
||||||
env: '["py39"]'
|
matrix:
|
||||||
os: windows-latest
|
os: [ubuntu-latest, windows-latest]
|
||||||
main-linux:
|
python-version: ['3.9', '3.10', '3.11', '3.12']
|
||||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
exclude:
|
||||||
with:
|
# Only run py39 on Windows
|
||||||
env: '["py39", "py310", "py311", "py312"]'
|
- os: windows-latest
|
||||||
os: ubuntu-latest
|
python-version: '3.10'
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: '3.11'
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install tox tox-gh-actions
|
||||||
|
|
||||||
|
- name: Test with tox
|
||||||
|
run: tox -- tests/languages/python_test.py tests/languages/pipenv_test.py
|
||||||
|
env:
|
||||||
|
TOXENV: py${{ matrix.python-version }}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from pre_commit.languages import julia
|
||||||
from pre_commit.languages import lua
|
from pre_commit.languages import lua
|
||||||
from pre_commit.languages import node
|
from pre_commit.languages import node
|
||||||
from pre_commit.languages import perl
|
from pre_commit.languages import perl
|
||||||
|
from pre_commit.languages import pipenv
|
||||||
from pre_commit.languages import pygrep
|
from pre_commit.languages import pygrep
|
||||||
from pre_commit.languages import python
|
from pre_commit.languages import python
|
||||||
from pre_commit.languages import r
|
from pre_commit.languages import r
|
||||||
|
|
@ -38,6 +39,7 @@ languages: dict[str, Language] = {
|
||||||
'lua': lua,
|
'lua': lua,
|
||||||
'node': node,
|
'node': node,
|
||||||
'perl': perl,
|
'perl': perl,
|
||||||
|
'pipenv': pipenv,
|
||||||
'pygrep': pygrep,
|
'pygrep': pygrep,
|
||||||
'python': python,
|
'python': python,
|
||||||
'r': r,
|
'r': r,
|
||||||
|
|
|
||||||
63
pre_commit/languages/pipenv.py
Normal file
63
pre_commit/languages/pipenv.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections.abc import Generator
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
import pre_commit.constants as C
|
||||||
|
from pre_commit import lang_base
|
||||||
|
from pre_commit.envcontext import envcontext
|
||||||
|
from pre_commit.languages import python
|
||||||
|
from pre_commit.prefix import Prefix
|
||||||
|
from pre_commit.util import cmd_output_b
|
||||||
|
|
||||||
|
ENVIRONMENT_DIR = 'pipenv_env'
|
||||||
|
get_default_version = python.get_default_version
|
||||||
|
run_hook = python.run_hook
|
||||||
|
|
||||||
|
def _assert_pipfile_exists(prefix: Prefix) -> None:
|
||||||
|
if not os.path.exists(os.path.join(prefix.prefix_dir, 'Pipfile')):
|
||||||
|
raise AssertionError(
|
||||||
|
'`language: pipenv` requires a Pipfile in the repository'
|
||||||
|
)
|
||||||
|
|
||||||
|
def health_check(prefix: Prefix, version: str) -> str | None:
|
||||||
|
_assert_pipfile_exists(prefix)
|
||||||
|
directory = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with in_env(prefix, version):
|
||||||
|
cmd_output_b('pipenv', 'check')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
return f'pipenv environment check failed: {e}'
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
|
||||||
|
directory = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||||
|
env = python.get_env_patch(directory)
|
||||||
|
with envcontext(env):
|
||||||
|
yield
|
||||||
|
|
||||||
|
def install_environment(
|
||||||
|
prefix: Prefix,
|
||||||
|
version: str,
|
||||||
|
additional_dependencies: Sequence[str],
|
||||||
|
) -> None:
|
||||||
|
_assert_pipfile_exists(prefix)
|
||||||
|
directory = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
|
||||||
|
|
||||||
|
with in_env(prefix, version):
|
||||||
|
# Initialize virtualenv if it doesn't exist
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
python_version = version if version != C.DEFAULT else f"{sys.version_info[0]}.{sys.version_info[1]}"
|
||||||
|
cmd_output_b('pipenv', '--python', python_version)
|
||||||
|
|
||||||
|
# Install dependencies from Pipfile
|
||||||
|
cmd_output_b('pipenv', 'install', '--dev')
|
||||||
|
|
||||||
|
# Install additional dependencies if specified
|
||||||
|
if additional_dependencies:
|
||||||
|
cmd_output_b('pipenv', 'install', *additional_dependencies)
|
||||||
|
|
@ -4,46 +4,37 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
EXCLUDED = frozenset((
|
EXCLUDED = {
|
||||||
('windows-latest', 'docker'),
|
('windows-latest', 'r'), # no r on windows
|
||||||
('windows-latest', 'docker_image'),
|
('windows-latest', 'swift'), # no swift on windows
|
||||||
('windows-latest', 'lua'),
|
}
|
||||||
('windows-latest', 'swift'),
|
|
||||||
))
|
|
||||||
|
|
||||||
|
ALLOWED_LANGUAGES = {'python', 'pipenv'}
|
||||||
|
|
||||||
def _always_run() -> frozenset[str]:
|
def _always_run() -> set[str]:
|
||||||
ret = ['.github/workflows/languages.yaml', 'testing/languages']
|
return {
|
||||||
ret.extend(
|
'pre_commit/languages/all.py',
|
||||||
os.path.join('pre_commit/resources', fname)
|
'pre_commit/languages/helpers.py',
|
||||||
for fname in os.listdir('pre_commit/resources')
|
'pre_commit/languages/__init__.py',
|
||||||
)
|
'pre_commit/languages/python.py', # python is a base for other languages
|
||||||
return frozenset(ret)
|
'pre_commit/languages/pipenv.py', # our new language
|
||||||
|
}
|
||||||
|
|
||||||
def _lang_files(lang: str) -> frozenset[str]:
|
|
||||||
prog = f'''\
|
|
||||||
import json
|
|
||||||
import os.path
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pre_commit.languages.{lang}
|
|
||||||
import tests.languages.{lang}_test
|
|
||||||
|
|
||||||
modules = sorted(
|
|
||||||
os.path.relpath(v.__file__)
|
|
||||||
for k, v in sys.modules.items()
|
|
||||||
if k.startswith(('pre_commit.', 'tests.', 'testing.'))
|
|
||||||
)
|
|
||||||
print(json.dumps(modules))
|
|
||||||
'''
|
|
||||||
out = json.loads(subprocess.check_output((sys.executable, '-c', prog)))
|
|
||||||
return frozenset(out)
|
|
||||||
|
|
||||||
|
def _lang_files(lang: str) -> set[str]:
|
||||||
|
cmd = ('git', 'grep', '-l', '')
|
||||||
|
env = dict(os.environ, GIT_LITERAL_PATHSPECS='1')
|
||||||
|
out = subprocess.check_output(
|
||||||
|
(*cmd, f':(glob,top)pre_commit/languages/{lang}.py'),
|
||||||
|
env=env,
|
||||||
|
).decode()
|
||||||
|
ret = set(out.splitlines())
|
||||||
|
ret.add(f'tests/languages/{lang}_test.py')
|
||||||
|
return ret
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
|
@ -51,9 +42,8 @@ def main() -> int:
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
langs = [
|
langs = [
|
||||||
os.path.splitext(fname)[0]
|
lang for lang in ALLOWED_LANGUAGES
|
||||||
for fname in sorted(os.listdir('pre_commit/languages'))
|
if os.path.exists(f'pre_commit/languages/{lang}.py')
|
||||||
if fname.endswith('.py') and fname != '__init__.py'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
triggers_all = _always_run()
|
triggers_all = _always_run()
|
||||||
|
|
@ -87,6 +77,5 @@ def main() -> int:
|
||||||
f.write(f'languages={json.dumps(matched)}\n')
|
f.write(f'languages={json.dumps(matched)}\n')
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
||||||
74
tests/languages/pipenv_test.py
Normal file
74
tests/languages/pipenv_test.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import pre_commit.constants as C
|
||||||
|
from pre_commit.languages import pipenv
|
||||||
|
from pre_commit.prefix import Prefix
|
||||||
|
from pre_commit.util import cmd_output
|
||||||
|
from testing.language_helpers import run_language
|
||||||
|
|
||||||
|
def test_health_check_no_pipfile(tmp_path):
|
||||||
|
with pytest.raises(AssertionError) as excinfo:
|
||||||
|
pipenv.health_check(Prefix(str(tmp_path)), C.DEFAULT)
|
||||||
|
assert '`language: pipenv` requires a Pipfile' in str(excinfo.value)
|
||||||
|
|
||||||
|
def _make_pipfile(path):
|
||||||
|
with open(os.path.join(path, 'Pipfile'), 'w') as f:
|
||||||
|
f.write('''\
|
||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
requests = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
pytest = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.9"
|
||||||
|
''')
|
||||||
|
|
||||||
|
def test_health_check_with_pipfile(tmp_path):
|
||||||
|
_make_pipfile(tmp_path)
|
||||||
|
assert pipenv.health_check(Prefix(str(tmp_path)), C.DEFAULT) is None
|
||||||
|
|
||||||
|
def test_install_environment(tmp_path):
|
||||||
|
_make_pipfile(tmp_path)
|
||||||
|
|
||||||
|
with mock.patch.object(pipenv, 'cmd_output_b') as mocked:
|
||||||
|
pipenv.install_environment(
|
||||||
|
Prefix(str(tmp_path)),
|
||||||
|
C.DEFAULT,
|
||||||
|
['black']
|
||||||
|
)
|
||||||
|
|
||||||
|
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
|
||||||
|
assert mocked.call_args_list == [
|
||||||
|
mock.call('pipenv', '--python', python_version),
|
||||||
|
mock.call('pipenv', 'install', '--dev'),
|
||||||
|
mock.call('pipenv', 'install', 'black'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_run_hook(tmp_path):
|
||||||
|
_make_pipfile(tmp_path)
|
||||||
|
|
||||||
|
# Create a simple Python script
|
||||||
|
script = '''\
|
||||||
|
#!/usr/bin/env python
|
||||||
|
print("Hello from pipenv!")
|
||||||
|
'''
|
||||||
|
tmp_path.joinpath('script.py').write_text(script)
|
||||||
|
|
||||||
|
ret = run_language(
|
||||||
|
tmp_path,
|
||||||
|
pipenv,
|
||||||
|
'python script.py',
|
||||||
|
)
|
||||||
|
assert ret == (0, b'Hello from pipenv!\n')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue