Run tests for all officially supported Django/Python combos

This PR updates the CI to run against the same combinations of Django and Python that Django officially supports.

It additionally adds a new script, that can automate this dance the next time: You simply run the script, and copy the results to tox.ini and setup.py.
This commit is contained in:
Emil Stenström 2023-05-03 22:01:25 +02:00 committed by GitHub
parent d57db39f51
commit b1ea325c5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 198 additions and 10 deletions

View file

@ -1,16 +1,17 @@
name: Run tests
on:
- push
- pull_request
- workflow_dispatch
push:
pull_request:
types: [opened, reopened]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v3

View file

@ -0,0 +1,180 @@
import re
import textwrap
from collections import defaultdict
from urllib import request
def get_supported_versions(url):
with request.urlopen(url) as response:
response_content = response.read()
content = response_content.decode("utf-8")
def parse_supported_versions(content):
def cut_by_content(content, cut_from, cut_to):
return content.split(cut_from)[1].split(cut_to)[0]
def keys_from_content(content):
return re.findall(r"<td>(.*?)</td>", content)
content = cut_by_content(
content,
'<span id="what-python-version-can-i-use-with-django">',
"</table>",
)
content = cut_by_content(content, '<tbody valign="top">', "</tbody>")
versions = keys_from_content(content)
version_dict = dict(zip(versions[::2], versions[1::2]))
django_to_python = {
version_to_tuple(python_version): [
version_to_tuple(version_string)
for version_string in re.findall(
r"(?<!\.)\d+\.\d+(?!\.)", django_versions
)
]
for python_version, django_versions in version_dict.items()
}
return django_to_python
return parse_supported_versions(content)
def get_latest_version(url):
with request.urlopen(url) as response:
response_content = response.read()
content = response_content.decode("utf-8")
version_string = re.findall(
r"The latest official version is (\d+\.\d)", content
)[0]
return version_to_tuple(version_string)
def version_to_tuple(version_string):
return tuple(int(num) for num in version_string.split("."))
def build_python_to_django(django_to_python, latest_version):
python_to_django = defaultdict(list)
for django_version, python_versions in django_to_python.items():
for python_version in python_versions:
if django_version <= latest_version:
python_to_django[python_version].append(django_version)
python_to_django = dict(python_to_django)
return python_to_django
def env_format(version_tuple, divider=""):
return divider.join(str(num) for num in version_tuple)
def build_tox_envlist(python_to_django):
lines = [
(
env_format(python_version),
",".join(env_format(version) for version in django_versions),
)
for python_version, django_versions in python_to_django.items()
]
lines = [f"py{a}-django{{{b}}}" for a, b in lines]
version_lines = "\n".join([version for version in lines])
return "envlist = \n" + textwrap.indent(version_lines, prefix=" ")
def build_gh_actions_envlist(python_to_django):
lines = [
(
env_format(python_version, divider="."),
env_format(python_version),
",".join(env_format(version) for version in django_versions),
)
for python_version, django_versions in python_to_django.items()
]
lines = [f"{a}: py{b}-django{{{c}}}" for a, b, c in lines]
version_lines = "\n".join([version for version in lines])
return "python = \n" + textwrap.indent(version_lines, prefix=" ")
def build_deps_envlist(python_to_django):
all_django_versions = set()
for django_versions in python_to_django.values():
for django_version in django_versions:
all_django_versions.add(django_version)
lines = [
(
env_format(django_version),
env_format(django_version, divider="."),
env_format(
(django_version[0], django_version[1] + 1), divider="."
),
)
for django_version in all_django_versions
]
lines = [f"django{a}: Django>={b},<{c}" for a, b, c in lines]
return "deps = \n" + textwrap.indent("\n".join(lines), prefix=" ")
def build_pypi_classifiers(python_to_django):
classifiers = []
all_python_versions = python_to_django.keys()
for python_version in all_python_versions:
classifiers.append(
f"Programming Language :: Python :: {env_format(python_version, divider='.')}"
)
all_django_versions = set()
for django_versions in python_to_django.values():
for django_version in django_versions:
all_django_versions.add(django_version)
classifiers.append("...")
for django_version in all_django_versions:
classifiers.append(
f"Programming Language :: Django :: {env_format(django_version, divider='.')}"
)
return "classifiers=[\n" + textwrap.indent(
"\n".join(classifiers), prefix=" "
)
def main():
django_to_python = get_supported_versions(
"https://docs.djangoproject.com/en/dev/faq/install/"
)
latest_version = get_latest_version(
"https://www.djangoproject.com/download/"
)
python_to_django = build_python_to_django(django_to_python, latest_version)
tox_envlist = build_tox_envlist(python_to_django)
print("Add this to tox.ini:\n")
print("[tox]")
print(tox_envlist)
print()
gh_actions_envlist = build_gh_actions_envlist(python_to_django)
print("[gh-actions]")
print(gh_actions_envlist)
print()
deps_envlist = build_deps_envlist(python_to_django)
print("[testenv]")
print(deps_envlist)
print()
print()
print("Add this to setup.py:\n")
pypi_classifiers = build_pypi_classifiers(python_to_django)
print(pypi_classifiers)
if __name__ == "__main__":
main()

View file

@ -28,9 +28,12 @@ setup(
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Operating System :: OS Independent",
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
],
)

16
tox.ini
View file

@ -5,9 +5,10 @@
envlist =
py36-django{32}
py37-django{32}
py38-django{32,40}
py39-django{32,40}
py310-django{40}
py38-django{32,40,41,42}
py39-django{32,40,41,42}
py310-django{32,40,41,42}
py311-django{41,42}
flake8
isort
@ -15,9 +16,10 @@ envlist =
python =
3.6: py36-django{32}
3.7: py37-django{32}
3.8: py38-django{32,40}
3.9: py39-django{32,40}
3.10: py310-django{40}, flake8, isort
3.8: py38-django{32,40,41,42}
3.9: py39-django{32,40,41,42}
3.10: py310-django{32,40,41,42}
3.11: py311-django{41,42}, flake8, isort
fail_on_no_env = True
[testenv]
@ -26,6 +28,8 @@ deps =
pytest-xdist
django32: Django>=3.2,<3.3
django40: Django>=4.0,<4.1
django41: Django>=4.1,<4.2
django42: Django>=4.2,<4.3
flake8
isort
commands = py.test {posargs}