mirror of
				https://github.com/cookiecutter/cookiecutter-django.git
				synced 2025-10-31 16:07:49 +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 | ||||
| to keep up to date. | ||||
| """ | ||||
| from __future__ import annotations | ||||
| 
 | ||||
| import os | ||||
| from typing import Sequence, TYPE_CHECKING | ||||
| from typing import NamedTuple, Sequence, TYPE_CHECKING | ||||
| 
 | ||||
| import requests | ||||
| import sys | ||||
|  | @ -26,6 +27,19 @@ REQUIREMENTS_DIR = ROOT / "{{cookiecutter.project_slug}}" / "requirements" | |||
| 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: | ||||
|     # "django" converts to "Django" on redirect | ||||
|     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"] | ||||
|     releases: Sequence[str] = package_info["releases"].keys() | ||||
|     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) | ||||
| 
 | ||||
| 
 | ||||
| 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("==") | ||||
|     name_without_extras = full_name.split("[", 1)[0] | ||||
|     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 | ||||
|     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 | ||||
|     _, current_version_str = get_name_and_version(line) | ||||
|     # 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")) | ||||
|     newer_versions: set[tuple] = set() | ||||
|     newer_versions: set[Version] = set() | ||||
|     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: | ||||
|             newer_versions.add(released_minor_version) | ||||
| 
 | ||||
|     needed_versions_str = ['.'.join(v) for v in sorted(newer_versions)] | ||||
|     return line, needed_versions_str | ||||
|     return current_minor_version, sorted(newer_versions, reverse=True) | ||||
| 
 | ||||
| 
 | ||||
| def get_first_digit(tokens) -> str: | ||||
|  | @ -97,14 +110,14 @@ VITAL_BUT_UNKNOWN = [ | |||
| 
 | ||||
| 
 | ||||
| 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.repo = self.github.get_repo(GITHUB_REPO) | ||||
| 
 | ||||
|         self.base_dj_version = base_dj_version | ||||
|         self.needed_dj_versions = needed_dj_versions | ||||
|         # (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 | ||||
|         # package information like a cache: | ||||
|  | @ -123,10 +136,11 @@ class GitHubManager: | |||
|         for requirements_file in self.requirements_files: | ||||
|             with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f: | ||||
|                 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) | ||||
|                         self.requirements[requirements_file][name] = ( | ||||
|                             version, get_package_info(name) | ||||
|                             version, | ||||
|                             get_package_info(name), | ||||
|                         ) | ||||
| 
 | ||||
|     def load_existing_issues(self): | ||||
|  | @ -144,32 +158,16 @@ class GitHubManager: | |||
|             ) | ||||
|         ) | ||||
|         for issue in issues: | ||||
|             try: | ||||
|                 dj_version = get_first_digit(issue.title.split(" ")) | ||||
|             except StopIteration: | ||||
|                 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_version_str = issue.title.split(" ")[-1] | ||||
|             issue_version = Version.parse(issue_version_str) | ||||
|             if self.base_dj_version > issue_version: | ||||
|                 issue.edit(state="closed") | ||||
|                 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: | ||||
|                 self.existing_issues[dj_version] = issue | ||||
|                 self.existing_issues[issue_version] = issue | ||||
| 
 | ||||
|     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 | ||||
|  | @ -191,24 +189,17 @@ class GitHubManager: | |||
|             return "", "❓" | ||||
| 
 | ||||
|         # Check classifiers if it includes Django | ||||
|         supported_dj_versions = [] | ||||
|         supported_dj_versions: list[Version] = [] | ||||
|         for classifier in package_info["info"]["classifiers"]: | ||||
|             # Usually in the form of "Framework :: Django :: 3.2" | ||||
|             tokens = classifier.split(" ") | ||||
|             for token in tokens: | ||||
|                 if token.lower() == "django": | ||||
|                     try: | ||||
|                         _version = get_first_digit(reversed(tokens)) | ||||
|                     except StopIteration: | ||||
|                         pass | ||||
|                     else: | ||||
|                         supported_dj_versions.append( | ||||
|                             float(".".join(_version.split(".", 2)[:2])) | ||||
|                         ) | ||||
|             if len(tokens) >= 5 and tokens[2].lower() == "django": | ||||
|                 version = Version.parse(tokens[4]) | ||||
|                 if len(version) == 2: | ||||
|                     supported_dj_versions.append(version) | ||||
| 
 | ||||
|         if supported_dj_versions: | ||||
|             needed_dj_version = float(needed_dj_version) | ||||
|             if any(x >= needed_dj_version for x in supported_dj_versions): | ||||
|             if any(v >= needed_dj_version for v in supported_dj_versions): | ||||
|                 return package_info["info"]["version"], "✅" | ||||
|             else: | ||||
|                 return "", "❌" | ||||
|  | @ -227,19 +218,19 @@ class GitHubManager: | |||
|     ] | ||||
| 
 | ||||
|     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: | ||||
|             return f"[{{}}]({next(item for item in urls if item)})" | ||||
|         except StopIteration: | ||||
|             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" | ||||
|         for _file in self.requirements_files: | ||||
|             requirements += ( | ||||
|                 _TABLE_HEADER.format_map( | ||||
|                     {"file": _file, "dj_version": needed_dj_version} | ||||
|                 ) | ||||
|             requirements += _TABLE_HEADER.format_map( | ||||
|                 {"file": _file, "dj_version": needed_dj_version} | ||||
|             ) | ||||
|             for package_name, (version, info) in self.requirements[_file].items(): | ||||
|                 compat_version, icon = self.get_compatibility( | ||||
|  | @ -251,8 +242,8 @@ class GitHubManager: | |||
|                 ) | ||||
|         return requirements | ||||
| 
 | ||||
|     def create_or_edit_issue(self, needed_dj_version, description): | ||||
|         if issue := self.existing_issues.get(str(needed_dj_version)): | ||||
|     def create_or_edit_issue(self, needed_dj_version: Version, description: str): | ||||
|         if issue := self.existing_issues.get(needed_dj_version): | ||||
|             issue.edit(body=description) | ||||
|         else: | ||||
|             self.repo.create_issue( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user