Skip to content

Commit

Permalink
Initial, read-only support for GitHub Actions
Browse files Browse the repository at this point in the history
See #22.
  • Loading branch information
mgedmin committed Nov 20, 2020
1 parent ed84988 commit 0b8b962
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 17 deletions.
5 changes: 3 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Changelog
=========

0.16.2 (unreleased)
0.17.0 (unreleased)
-------------------

- Nothing changed yet.
- Initial supprot for GitHub Actions (`issue 22
<https://github.com/mgedmin/check-python-versions/issues/22>`_).


0.16.1 (2020-11-08)
Expand Down
2 changes: 2 additions & 0 deletions src/check_python_versions/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ def update_versions(
replacements: ReplacementDict = {}

for source in ALL_SOURCES:
if source.update is None:
continue
if only and source.filename not in only:
continue
pathname = os.path.join(where, source.filename)
Expand Down
2 changes: 2 additions & 0 deletions src/check_python_versions/sources/all.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .setup_py import SetupClassifiers, SetupPythonRequires
from .tox import Tox
from .travis import Travis
from .github import GitHubActions


# The order here is only mildly important: it's used for presentation.
Expand All @@ -13,6 +14,7 @@
SetupPythonRequires,
Tox,
Travis,
GitHubActions,
Appveyor,
Manylinux,
]
2 changes: 1 addition & 1 deletion src/check_python_versions/sources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ class Source(typing.NamedTuple):
title: str
filename: str
extract: ExtractorFn
update: UpdaterFn
update: Optional[UpdaterFn] = None
77 changes: 77 additions & 0 deletions src/check_python_versions/sources/github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Support for GitHub Actions.
GitHub Actions are very flexible, so this code is going to make some
simplifying assumptions:
- your workflow is in .github/workflows/tests.yml
- you use a matrix strategy
- on 'python-version' that contains python versions, or
- on 'config' that contains lists of [python_version, tox_env]
"""

from typing import List, Union

import yaml

from .base import Source
from ..utils import FileOrFilename, open_file
from ..versions import SortedVersionList, Version


GHA_WORKFLOW_FILE = '.github/workflows/tests.yml'


def get_gha_python_versions(
filename: FileOrFilename = GHA_WORKFLOW_FILE,
) -> SortedVersionList:
"""Extract supported Python versions from a GitHub workflow."""
with open_file(filename) as fp:
conf = yaml.safe_load(fp)

versions: List[Version] = []
for job_name, job in conf.get('jobs', {}).items():
matrix = job.get('strategy', {}).get('matrix', {})
if 'python-version' in matrix:
versions.extend(
e for e in map(parse_gh_ver, matrix['python-version']) if e)
if 'config' in matrix:
versions.extend(
parse_gh_ver(c[0])
for c in matrix['config']
if isinstance(c, list)
)

return sorted(set(versions))


def parse_gh_ver(v: Union[str, float]) -> Version:
"""Parse Python versions used for actions/setup-python@v2.
This format is not fully well documented. There's support for
specifying things like
- "3.x" (latest minor in Python 3.x; currently 3.9)
- "3.7" (latest bugfix in Python 3.7)
- "3.7.2" (specific version to be downloaded and installed)
- "pypy2"/"pypy3"
https://github.com/actions/python-versions/blob/main/versions-manifest.json
contains a list of supported CPython versions that can be downloaded
and installed; this includes prereleases, but doesn't include PyPy.
"""
v = str(v)
if v.startswith('pypy3'):
return Version.from_string('PyPy3')
elif v.startswith('pypy2'):
return Version.from_string('PyPy')
else:
return Version.from_string(v)


GitHubActions = Source(
title=GHA_WORKFLOW_FILE,
filename=GHA_WORKFLOW_FILE,
extract=get_gha_python_versions,
update=None,
)
115 changes: 115 additions & 0 deletions tests/sources/test_github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import textwrap
from io import StringIO
from typing import List

import pytest

from check_python_versions.sources.github import (
get_gha_python_versions,
parse_gh_ver,
)
from check_python_versions.versions import Version


def v(versions: List[str]) -> List[Version]:
return [Version.from_string(v) for v in versions]


def test_get_gha_python_versions():
tests_yml = StringIO(textwrap.dedent("""\
name: Python package
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
pip install -r requirements.txt
- name: Test with pytest
run: |
pytest
"""))
tests_yml.name = '.github/workflows/tests.yml'
assert get_gha_python_versions(tests_yml) == v([
'2.7', '3.5', '3.6', '3.7', '3.8',
])


def test_get_gha_python_versions_zopefoundation():
tests_yml = StringIO(textwrap.dedent("""\
name: tests
on:
push:
branches: [ master ]
pull_request:
schedule:
- cron: '0 12 * * 0' # run once a week on Sunday
jobs:
build:
strategy:
matrix:
config:
# [Python version, tox env]
- ["3.8", "lint"]
- ["2.7", "py27"]
- ["3.5", "py35"]
- ["3.6", "py36"]
- ["3.7", "py37"]
- ["3.8", "py38"]
- ["3.9", "py39"]
- ["pypy2", "pypy"]
- ["pypy3", "pypy3"]
- ["3.8", "coverage"]
runs-on: ubuntu-latest
name: ${{ matrix.config[1] }}
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.config[0] }}
- ...
"""))
tests_yml.name = '.github/workflows/tests.yml'
assert get_gha_python_versions(tests_yml) == v([
'2.7', '3.5', '3.6', '3.7', '3.8', '3.9', 'PyPy', 'PyPy3',
])


def test_get_gha_python_versions_no_version_matrix():
tests_yml = StringIO(textwrap.dedent("""\
name: Python package
on: [push]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- ...
"""))
tests_yml.name = '.github/workflows/tests.yml'
assert get_gha_python_versions(tests_yml) == []


@pytest.mark.parametrize('s, expected', [
(3.6, '3.6'),
('3.7', '3.7'),
('pypy2', 'PyPy'),
('pypy3', 'PyPy3'),
])
def test_parse_gh_ver(s, expected):
assert parse_gh_ver(s) == Version.from_string(expected)
28 changes: 14 additions & 14 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_check_unknown(tmp_path, capsys):
"""))
assert cpv.check_versions(tmp_path) is True
assert capsys.readouterr().out == textwrap.dedent("""\
setup.py says: (empty)
setup.py says: (empty)
""")


Expand All @@ -122,7 +122,7 @@ def test_check_minimal(tmp_path, capsys):
"""))
assert cpv.check_versions(tmp_path) is True
assert capsys.readouterr().out == textwrap.dedent("""\
setup.py says: 2.7, 3.6
setup.py says: 2.7, 3.6
""")


Expand All @@ -145,8 +145,8 @@ def test_check_mismatch(tmp_path, capsys):
"""))
assert cpv.check_versions(tmp_path) is False
assert capsys.readouterr().out == textwrap.dedent("""\
setup.py says: 2.7, 3.6
tox.ini says: 2.7
setup.py says: 2.7, 3.6
tox.ini says: 2.7
""")


Expand All @@ -164,8 +164,8 @@ def test_check_expectation(tmp_path, capsys):
"""))
assert not cpv.check_versions(tmp_path, expect=v(['2.7', '3.6', '3.7']))
assert capsys.readouterr().out == textwrap.dedent("""\
setup.py says: 2.7, 3.6
expected: 2.7, 3.6, 3.7
setup.py says: 2.7, 3.6
expected: 2.7, 3.6, 3.7
""")


Expand All @@ -188,7 +188,7 @@ def test_check_only(tmp_path, capsys):
"""))
assert cpv.check_versions(tmp_path, only='tox.ini')
assert capsys.readouterr().out == textwrap.dedent("""\
tox.ini says: 2.7
tox.ini says: 2.7
""")


Expand Down Expand Up @@ -525,8 +525,8 @@ def test_main_only(monkeypatch, capsys, tmp_path):
assert (
capsys.readouterr().out + '\n'
).replace(str(tmp_path) + os.path.sep, 'tmp/') == textwrap.dedent("""\
setup.py says: 2.7, 3.6
tox.ini says: 2.7, 3.6
setup.py says: 2.7, 3.6
tox.ini says: 2.7, 3.6
""")

Expand Down Expand Up @@ -556,8 +556,8 @@ def test_main_multiple(monkeypatch, capsys, tmp_path):
).replace(str(tmp_path) + os.path.sep, 'tmp/') == textwrap.dedent("""\
tmp/a:
setup.py says: 2.7, 3.6
expected: 3.6, 3.7
setup.py says: 2.7, 3.6
expected: 3.6, 3.7
tmp/b:
Expand Down Expand Up @@ -640,7 +640,7 @@ def test_main_update(monkeypatch, capsys, tmp_path):
Write changes to tmp/setup.py? [y/N]
setup.py says: 2.7, 3.6, 3.7, 3.8
setup.py says: 2.7, 3.6, 3.7, 3.8
""")
assert (tmp_path / "setup.py").read_text() == textwrap.dedent("""\
from setuptools import setup
Expand Down Expand Up @@ -693,7 +693,7 @@ def test_main_update_rejected(monkeypatch, capsys, tmp_path):
Write changes to tmp/setup.py? [y/N]
setup.py says: 2.7, 3.6
setup.py says: 2.7, 3.6
""")
assert (tmp_path / "setup.py").read_text() == textwrap.dedent("""\
from setuptools import setup
Expand Down Expand Up @@ -779,7 +779,7 @@ def test_main_update_dry_run(monkeypatch, capsys, tmp_path):
.expandtabs()
.replace(' \n', '\n\n')
) == textwrap.dedent("""\
setup.py says: 2.7, 3.6, 3.7, 3.8
setup.py says: 2.7, 3.6, 3.7, 3.8
""")
assert (tmp_path / "setup.py").read_text() == textwrap.dedent("""\
from setuptools import setup
Expand Down

0 comments on commit 0b8b962

Please sign in to comment.