Skip to content

Commit

Permalink
Add Trove Classifier Modernizer (#253)
Browse files Browse the repository at this point in the history
* feat: Add new Trove Classifier
  • Loading branch information
aht007 authored Feb 2, 2022
1 parent 8762e5a commit 0806b89
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 5 deletions.
2 changes: 1 addition & 1 deletion edx_repo_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@

__version__ = '0.4.1'
__version__ = '0.5.0'
6 changes: 3 additions & 3 deletions edx_repo_tools/codemods/django3/__init__.py
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
62 changes: 62 additions & 0 deletions edx_repo_tools/codemods/django3/setup_file_modernizer.py
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()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def get_version(*file_paths):
'modernize_openedx_yaml = edx_repo_tools.modernize_openedx_yaml:main',
'modernize_github_actions = edx_repo_tools.codemods.django3.github_actions_modernizer:main',
'modernize_github_actions_django = edx_repo_tools.codemods.django3.github_actions_modernizer_django:main',
'modernize_setup_file = edx_repo_tools.codemods.django3.setup_file_modernizer:main',
'add_common_constraint = edx_repo_tools.add_common_constraint:main',
'remove_python2_unicode_compatible = edx_repo_tools.codemods.django3.remove_python2_unicode_compatible:main',
'replace_unicode_with_str = edx_repo_tools.codemods.django3.replace_unicode_with_str:main',
Expand Down
117 changes: 117 additions & 0 deletions tests/sample_files/sample_setup_file.py
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'),
)
2 changes: 1 addition & 1 deletion tests/test_actions_modernizer_django.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import os
import shutil
from os.path import dirname, basename, join
from os.path import basename, dirname, join

from edx_repo_tools.codemods.django3 import GithubCIDjangoModernizer
from edx_repo_tools.utils import YamlLoader
Expand Down
39 changes: 39 additions & 0 deletions tests/test_setup_file_modernizer.py
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

0 comments on commit 0806b89

Please sign in to comment.