mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-02-26 00:10:42 +03:00
Refactor how versions are handled
This commit is contained in:
parent
007fd0206e
commit
44ba3cf19e
|
@ -6,9 +6,10 @@ patches, only comparing major and minor version numbers.
|
||||||
This script handles when there are multiple Django versions that need
|
This script handles when there are multiple Django versions that need
|
||||||
to keep up to date.
|
to keep up to date.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Sequence, TYPE_CHECKING
|
from typing import NamedTuple, Sequence, TYPE_CHECKING
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import sys
|
import sys
|
||||||
|
@ -26,6 +27,19 @@ REQUIREMENTS_DIR = ROOT / "{{cookiecutter.project_slug}}" / "requirements"
|
||||||
GITHUB_REPO = "cookiecutter/cookiecutter-django"
|
GITHUB_REPO = "cookiecutter/cookiecutter-django"
|
||||||
|
|
||||||
|
|
||||||
|
class Version(NamedTuple):
|
||||||
|
major: str
|
||||||
|
minor: str
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.major}.{self.minor}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, version_str: str) -> Version:
|
||||||
|
major, minor, *_ = version_str.split(".")
|
||||||
|
return cls(major=major, minor=minor)
|
||||||
|
|
||||||
|
|
||||||
def get_package_info(package: str) -> dict:
|
def get_package_info(package: str) -> dict:
|
||||||
# "django" converts to "Django" on redirect
|
# "django" converts to "Django" on redirect
|
||||||
r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True)
|
r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True)
|
||||||
|
@ -40,17 +54,17 @@ def get_package_versions(package_info: dict, reverse=True, *, include_pre=False)
|
||||||
# package version, you could simple do get_package_info()["info"]["version"]
|
# package version, you could simple do get_package_info()["info"]["version"]
|
||||||
releases: Sequence[str] = package_info["releases"].keys()
|
releases: Sequence[str] = package_info["releases"].keys()
|
||||||
if not include_pre:
|
if not include_pre:
|
||||||
releases = [x for x in releases if x.replace(".", "").isdigit()]
|
releases = [r for r in releases if r.replace(".", "").isdigit()]
|
||||||
return sorted(releases, reverse=reverse)
|
return sorted(releases, reverse=reverse)
|
||||||
|
|
||||||
|
|
||||||
def get_name_and_version(requirements_line: str) -> tuple[str, str]:
|
def get_name_and_version(requirements_line: str) -> tuple[str, ...]:
|
||||||
full_name, version = requirements_line.split(" ", 1)[0].split("==")
|
full_name, version = requirements_line.split(" ", 1)[0].split("==")
|
||||||
name_without_extras = full_name.split("[", 1)[0]
|
name_without_extras = full_name.split("[", 1)[0]
|
||||||
return name_without_extras, version
|
return name_without_extras, version
|
||||||
|
|
||||||
|
|
||||||
def get_all_latest_django_versions() -> tuple[str, list[str]]:
|
def get_all_latest_django_versions() -> tuple[Version, list[Version]]:
|
||||||
"""
|
"""
|
||||||
Grabs all Django versions that are worthy of a GitHub issue. Depends on
|
Grabs all Django versions that are worthy of a GitHub issue. Depends on
|
||||||
if Django versions has higher major version or minor version
|
if Django versions has higher major version or minor version
|
||||||
|
@ -67,16 +81,15 @@ def get_all_latest_django_versions() -> tuple[str, list[str]]:
|
||||||
# Begin parsing and verification
|
# Begin parsing and verification
|
||||||
_, current_version_str = get_name_and_version(line)
|
_, current_version_str = get_name_and_version(line)
|
||||||
# Get a tuple of (major, minor) - ignoring patch version
|
# Get a tuple of (major, minor) - ignoring patch version
|
||||||
current_minor_version = tuple(current_version_str.split(".")[:2])
|
current_minor_version = Version.parse(current_version_str)
|
||||||
all_django_versions = get_package_versions(get_package_info("django"))
|
all_django_versions = get_package_versions(get_package_info("django"))
|
||||||
newer_versions: set[tuple] = set()
|
newer_versions: set[Version] = set()
|
||||||
for version_str in all_django_versions:
|
for version_str in all_django_versions:
|
||||||
released_minor_version = tuple(version_str.split(".")[:2])
|
released_minor_version = Version.parse(version_str)
|
||||||
if released_minor_version > current_minor_version:
|
if released_minor_version > current_minor_version:
|
||||||
newer_versions.add(released_minor_version)
|
newer_versions.add(released_minor_version)
|
||||||
|
|
||||||
needed_versions_str = ['.'.join(v) for v in sorted(newer_versions)]
|
return current_minor_version, sorted(newer_versions, reverse=True)
|
||||||
return line, needed_versions_str
|
|
||||||
|
|
||||||
|
|
||||||
def get_first_digit(tokens) -> str:
|
def get_first_digit(tokens) -> str:
|
||||||
|
@ -97,14 +110,14 @@ VITAL_BUT_UNKNOWN = [
|
||||||
|
|
||||||
|
|
||||||
class GitHubManager:
|
class GitHubManager:
|
||||||
def __init__(self, base_dj_version: str, needed_dj_versions: list[str]):
|
def __init__(self, base_dj_version: Version, needed_dj_versions: list[Version]):
|
||||||
self.github = Github(os.getenv("GITHUB_TOKEN", None))
|
self.github = Github(os.getenv("GITHUB_TOKEN", None))
|
||||||
self.repo = self.github.get_repo(GITHUB_REPO)
|
self.repo = self.github.get_repo(GITHUB_REPO)
|
||||||
|
|
||||||
self.base_dj_version = base_dj_version
|
self.base_dj_version = base_dj_version
|
||||||
self.needed_dj_versions = needed_dj_versions
|
self.needed_dj_versions = needed_dj_versions
|
||||||
# (major+minor) Version and description
|
# (major+minor) Version and description
|
||||||
self.existing_issues: dict[str, "Issue"] = {}
|
self.existing_issues: dict[Version, Issue] = {}
|
||||||
|
|
||||||
# Load all requirements from our requirements files and preload their
|
# Load all requirements from our requirements files and preload their
|
||||||
# package information like a cache:
|
# package information like a cache:
|
||||||
|
@ -123,10 +136,11 @@ class GitHubManager:
|
||||||
for requirements_file in self.requirements_files:
|
for requirements_file in self.requirements_files:
|
||||||
with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f:
|
with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f:
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
if "==" in line and not line.startswith('{%'):
|
if "==" in line and not line.startswith("{%"):
|
||||||
name, version = get_name_and_version(line)
|
name, version = get_name_and_version(line)
|
||||||
self.requirements[requirements_file][name] = (
|
self.requirements[requirements_file][name] = (
|
||||||
version, get_package_info(name)
|
version,
|
||||||
|
get_package_info(name),
|
||||||
)
|
)
|
||||||
|
|
||||||
def load_existing_issues(self):
|
def load_existing_issues(self):
|
||||||
|
@ -144,32 +158,16 @@ class GitHubManager:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for issue in issues:
|
for issue in issues:
|
||||||
try:
|
issue_version_str = issue.title.split(" ")[-1]
|
||||||
dj_version = get_first_digit(issue.title.split(" "))
|
issue_version = Version.parse(issue_version_str)
|
||||||
except StopIteration:
|
if self.base_dj_version > issue_version:
|
||||||
try:
|
|
||||||
# Some padding; randomly chose 4 to make sure we don't get a random
|
|
||||||
# version number from a package that's not Django
|
|
||||||
dj_version = get_first_digit(issue.body.split(" ", 4))
|
|
||||||
except StopIteration:
|
|
||||||
print(
|
|
||||||
f"Found issue {issue.title} that had an invalid syntax",
|
|
||||||
"(Did not have a Django version number in the title or body's"
|
|
||||||
f" first word. Issue number: [{issue.id}]({issue.url}))"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
if self.base_dj_version > dj_version:
|
|
||||||
issue.edit(state="closed")
|
issue.edit(state="closed")
|
||||||
print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))")
|
print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))")
|
||||||
try:
|
|
||||||
self.needed_dj_versions.remove(dj_version)
|
|
||||||
except ValueError:
|
|
||||||
print("Something weird happened. Continuing anyway (Warning ID: 1)")
|
|
||||||
else:
|
else:
|
||||||
self.existing_issues[dj_version] = issue
|
self.existing_issues[issue_version] = issue
|
||||||
|
|
||||||
def get_compatibility(
|
def get_compatibility(
|
||||||
self, package_name: str, package_info: dict, needed_dj_version
|
self, package_name: str, package_info: dict, needed_dj_version: Version
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Verify compatibility via setup.py classifiers. If Django is not in the
|
Verify compatibility via setup.py classifiers. If Django is not in the
|
||||||
|
@ -191,24 +189,17 @@ class GitHubManager:
|
||||||
return "", "❓"
|
return "", "❓"
|
||||||
|
|
||||||
# Check classifiers if it includes Django
|
# Check classifiers if it includes Django
|
||||||
supported_dj_versions = []
|
supported_dj_versions: list[Version] = []
|
||||||
for classifier in package_info["info"]["classifiers"]:
|
for classifier in package_info["info"]["classifiers"]:
|
||||||
# Usually in the form of "Framework :: Django :: 3.2"
|
# Usually in the form of "Framework :: Django :: 3.2"
|
||||||
tokens = classifier.split(" ")
|
tokens = classifier.split(" ")
|
||||||
for token in tokens:
|
if len(tokens) >= 5 and tokens[2].lower() == "django":
|
||||||
if token.lower() == "django":
|
version = Version.parse(tokens[4])
|
||||||
try:
|
if len(version) == 2:
|
||||||
_version = get_first_digit(reversed(tokens))
|
supported_dj_versions.append(version)
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
supported_dj_versions.append(
|
|
||||||
float(".".join(_version.split(".", 2)[:2]))
|
|
||||||
)
|
|
||||||
|
|
||||||
if supported_dj_versions:
|
if supported_dj_versions:
|
||||||
needed_dj_version = float(needed_dj_version)
|
if any(v >= needed_dj_version for v in supported_dj_versions):
|
||||||
if any(x >= needed_dj_version for x in supported_dj_versions):
|
|
||||||
return package_info["info"]["version"], "✅"
|
return package_info["info"]["version"], "✅"
|
||||||
else:
|
else:
|
||||||
return "", "❌"
|
return "", "❌"
|
||||||
|
@ -227,19 +218,19 @@ class GitHubManager:
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_md_home_page_url(self, package_info: dict):
|
def _get_md_home_page_url(self, package_info: dict):
|
||||||
urls = [package_info["info"].get(x) for x in self.HOME_PAGE_URL_KEYS]
|
urls = [
|
||||||
|
package_info["info"].get(url_key) for url_key in self.HOME_PAGE_URL_KEYS
|
||||||
|
]
|
||||||
try:
|
try:
|
||||||
return f"[{{}}]({next(item for item in urls if item)})"
|
return f"[{{}}]({next(item for item in urls if item)})"
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return "{}"
|
return "{}"
|
||||||
|
|
||||||
def generate_markdown(self, needed_dj_version: str):
|
def generate_markdown(self, needed_dj_version: Version):
|
||||||
requirements = f"{needed_dj_version} requirements tables\n\n"
|
requirements = f"{needed_dj_version} requirements tables\n\n"
|
||||||
for _file in self.requirements_files:
|
for _file in self.requirements_files:
|
||||||
requirements += (
|
requirements += _TABLE_HEADER.format_map(
|
||||||
_TABLE_HEADER.format_map(
|
{"file": _file, "dj_version": needed_dj_version}
|
||||||
{"file": _file, "dj_version": needed_dj_version}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
for package_name, (version, info) in self.requirements[_file].items():
|
for package_name, (version, info) in self.requirements[_file].items():
|
||||||
compat_version, icon = self.get_compatibility(
|
compat_version, icon = self.get_compatibility(
|
||||||
|
@ -251,8 +242,8 @@ class GitHubManager:
|
||||||
)
|
)
|
||||||
return requirements
|
return requirements
|
||||||
|
|
||||||
def create_or_edit_issue(self, needed_dj_version, description):
|
def create_or_edit_issue(self, needed_dj_version: Version, description: str):
|
||||||
if issue := self.existing_issues.get(str(needed_dj_version)):
|
if issue := self.existing_issues.get(needed_dj_version):
|
||||||
issue.edit(body=description)
|
issue.edit(body=description)
|
||||||
else:
|
else:
|
||||||
self.repo.create_issue(
|
self.repo.create_issue(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user