-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Trove Classifier Modernizer (#253)
* feat: Add new Trove Classifier
- Loading branch information
Showing
7 changed files
with
224 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
|
||
__version__ = '0.4.1' | ||
__version__ = '0.5.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
from .tox_modernizer import ConfigReader, ToxModernizer | ||
from .travis_modernizer import TravisModernizer | ||
from .travis_modernizer import DJANGO_PATTERN | ||
from .github_actions_modernizer import GithubCIModernizer | ||
from .github_actions_modernizer_django import GithubCIDjangoModernizer | ||
from .setup_file_modernizer import SetupFileModernizer | ||
from .tox_modernizer import ConfigReader, ToxModernizer | ||
from .travis_modernizer import DJANGO_PATTERN, TravisModernizer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
""" | ||
Codemod to modernize setup file. | ||
""" | ||
import json | ||
import os | ||
import re | ||
from copy import deepcopy | ||
|
||
import click | ||
|
||
|
||
class SetupFileModernizer: | ||
""" | ||
Django32 modernizer for updating setup files. | ||
""" | ||
old_classifiers_regex = r"(?!\s\s+'Framework :: Django :: 3.2')(\s\s+'Framework\s+::\s+Django\s+::\s+[0-3]+\.[0-2]+',)" | ||
most_recent_classifier_regex = r"\s\s'Framework :: Django :: 3.2',\n" | ||
# Keep the new classifiers in descending order i.e Framework :: Django :: 4.1 then Framework :: Django :: 4.0 so they are sorted in the file | ||
new_trove_classifiers = ["\t\t'Framework :: Django :: 4.0',\n"] | ||
|
||
def __init__(self, path=None) -> None: | ||
self.setup_file_path = path | ||
|
||
def _update_classifiers(self) -> None: | ||
file_data = open(self.setup_file_path).read() | ||
file_data = self._remove_outdated_classifiers(file_data) | ||
file_data = self._add_new_classifiers(file_data) | ||
self._write_data_to_file(file_data) | ||
|
||
def _remove_outdated_classifiers(self, file_data) -> str: | ||
modified_file_data = re.sub(self.old_classifiers_regex, '', file_data) | ||
return modified_file_data | ||
|
||
def _add_new_classifiers(self, file_data) -> str: | ||
res = re.search(self.most_recent_classifier_regex, file_data) | ||
end_index_of_most_recent_classifier = res.end() | ||
modified_file_data = file_data | ||
for classifier in self.new_trove_classifiers: | ||
modified_file_data = (modified_file_data[:end_index_of_most_recent_classifier] + | ||
classifier + | ||
modified_file_data[end_index_of_most_recent_classifier:]) | ||
return modified_file_data | ||
|
||
def _write_data_to_file(self, file_data) -> None: | ||
with open(self.setup_file_path, 'w') as setup_file: | ||
setup_file.write(file_data) | ||
|
||
def update_setup_file(self) -> None: | ||
self._update_classifiers() | ||
|
||
|
||
@click.command() | ||
@click.option( | ||
'--path', default='setup.py', | ||
help="Path to setup.py File") | ||
def main(path): | ||
setup_file_modernizer = SetupFileModernizer(path) | ||
setup_file_modernizer.update_setup_file() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
#!/usr/bin/env python | ||
import os | ||
import re | ||
|
||
from setuptools import find_packages, setup | ||
|
||
|
||
def is_requirement(line): | ||
""" | ||
Return True if the requirement line is a package requirement. | ||
Returns: | ||
bool: True if the line is not blank, a comment, | ||
a URL, or an included file | ||
""" | ||
# UPDATED VIA SEMGREP - if you need to remove/modify this method remove this line and add a comment specifying why | ||
|
||
return line and line.strip() and not line.startswith(('-r', '#', '-e', 'git+', '-c')) | ||
|
||
|
||
def load_requirements(*requirements_paths): | ||
""" | ||
Load all requirements from the specified requirements files. | ||
Requirements will include any constraints from files specified | ||
with -c in the requirements files. | ||
Returns a list of requirement strings. | ||
""" | ||
# UPDATED VIA SEMGREP - if you need to remove/modify this method remove this line and add a comment specifying why. | ||
# small change from original SEMGREP update to allow packages with [] in the name | ||
|
||
requirements = {} | ||
constraint_files = set() | ||
|
||
# groups "my-package-name<=x.y.z,..." into ("my-package-name", "<=x.y.z,...") | ||
requirement_line_regex = re.compile(r"([a-zA-Z0-9-_.\[\]]+)([<>=][^#\s]+)?") | ||
|
||
def add_version_constraint_or_raise(current_line, current_requirements, add_if_not_present): | ||
regex_match = requirement_line_regex.match(current_line) | ||
if regex_match: | ||
package = regex_match.group(1) | ||
version_constraints = regex_match.group(2) | ||
existing_version_constraints = current_requirements.get(package, None) | ||
# it's fine to add constraints to an unconstrained package, but raise an error if there are already | ||
# constraints in place | ||
if existing_version_constraints and existing_version_constraints != version_constraints: | ||
raise BaseException(f'Multiple constraint definitions found for {package}:' | ||
f' "{existing_version_constraints}" and "{version_constraints}".' | ||
f'Combine constraints into one location with {package}' | ||
f'{existing_version_constraints},{version_constraints}.') | ||
if add_if_not_present or package in current_requirements: | ||
current_requirements[package] = version_constraints | ||
|
||
# process .in files and store the path to any constraint files that are pulled in | ||
for path in requirements_paths: | ||
with open(path) as reqs: | ||
for line in reqs: | ||
if is_requirement(line): | ||
add_version_constraint_or_raise(line, requirements, True) | ||
if line and line.startswith('-c') and not line.startswith('-c http'): | ||
constraint_files.add(os.path.dirname(path) + '/' + line.split('#')[0].replace('-c', '').strip()) | ||
|
||
# process constraint files and add any new constraints found to existing requirements | ||
for constraint_file in constraint_files: | ||
with open(constraint_file) as reader: | ||
for line in reader: | ||
if is_requirement(line): | ||
add_version_constraint_or_raise(line, requirements, False) | ||
|
||
# process back into list of pkg><=constraints strings | ||
constrained_requirements = [f'{pkg}{version or ""}' for (pkg, version) in sorted(requirements.items())] | ||
return constrained_requirements | ||
|
||
|
||
def get_version(*file_paths): | ||
""" | ||
Extract the version string from the file at the given relative path fragments. | ||
""" | ||
filename = os.path.join(os.path.dirname(__file__), *file_paths) | ||
with open(filename, encoding='utf-8') as opened_file: | ||
version_file = opened_file.read() | ||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", | ||
version_file, re.M) | ||
if version_match: | ||
return version_match.group(1) | ||
raise RuntimeError('Unable to find version string.') | ||
|
||
|
||
VERSION = get_version("edx_rest_framework_extensions", "__init__.py") | ||
|
||
|
||
setup( | ||
name='edx-drf-extensions', | ||
version=VERSION, | ||
description='edX extensions of Django REST Framework', | ||
author='edX', | ||
author_email='[email protected]', | ||
url='https://github.com/edx/edx-drf-extensions', | ||
license='Apache 2.0', | ||
classifiers=[ | ||
'Development Status :: 5 - Production/Stable', | ||
'Environment :: Web Environment', | ||
'Intended Audience :: Developers', | ||
'License :: OSI Approved :: Apache Software License', | ||
'Operating System :: OS Independent', | ||
'Programming Language :: Python :: 3', | ||
'Programming Language :: Python :: 3.8', | ||
'Framework :: Django', | ||
'Framework :: Django :: 2.2', | ||
'Framework :: Django :: 3.0', | ||
'Framework :: Django :: 3.1', | ||
'Framework :: Django :: 3.2', | ||
], | ||
packages=find_packages(exclude=["tests"]), | ||
install_requires=load_requirements('requirements/base.in'), | ||
tests_require=load_requirements('requirements/test.in'), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
""" | ||
Tests for setup file modernizer | ||
""" | ||
import os | ||
import shutil | ||
from os.path import basename, dirname, join | ||
|
||
from edx_repo_tools.codemods.django3.setup_file_modernizer import SetupFileModernizer | ||
|
||
|
||
def setup_local_copy(filepath, tmpdir): | ||
current_directory = dirname(__file__) | ||
local_file = join(current_directory, filepath) | ||
temp_file_path = str(join(tmpdir, basename(filepath))) | ||
shutil.copy2(local_file, temp_file_path) | ||
return temp_file_path | ||
|
||
def test_remove_existing_classifiers(tmpdir): | ||
""" | ||
Test the case where old classifiers are removed | ||
""" | ||
test_file = setup_local_copy("sample_files/sample_setup_file.py", tmpdir) | ||
setup_file_modernizer = SetupFileModernizer() | ||
file_data = open(test_file).read() | ||
updated_file_data = setup_file_modernizer._remove_outdated_classifiers(file_data) | ||
assert "'Framework :: Django :: 3.1'" not in updated_file_data | ||
assert "'Framework :: Django :: 3.0'" not in updated_file_data | ||
assert "'Framework :: Django :: 2.2'" not in updated_file_data | ||
assert "'Framework :: Django :: 3.2'" in updated_file_data | ||
|
||
def test_add_new_classifiers(tmpdir): | ||
""" | ||
Test the case where new classifiers are added | ||
""" | ||
test_file = setup_local_copy("sample_files/sample_setup_file.py", tmpdir) | ||
setup_file_modernizer = SetupFileModernizer() | ||
file_data = open(test_file).read() | ||
updated_file_data = setup_file_modernizer._add_new_classifiers(file_data) | ||
assert "'Framework :: Django :: 4.0'" in updated_file_data |