From 66732556790926577881590345a1451fb565c8d5 Mon Sep 17 00:00:00 2001 From: Kieran Ryan Date: Sun, 22 Dec 2024 22:05:56 +0000 Subject: [PATCH] feat: drop support for Python 2 - Removed Python 2 references and infrastructure - Automatically applied [ruff fixes for Python 2 deprecations](https://docs.astral.sh/ruff/rules/#pyupgrade-up): - UP004 - useless-object-inheritance - Class {name} inherits from object - UP008 - super-call-with-parameters - Use super() instead of super(__class__, self) - UP009 - utf8-encoding-declaration - UTF-8 encoding declaration is unnecessary - UP020 - open-alias - Use builtin open - UP025 - unicode-kind-prefix - Remove unicode literals from strings - UP030 - format-literals - Use implicit references for positional format fields - Manually resolved `UP036` deprecations (outdated-version-block) - Dropped deprecated `MAINTAINER` property from `Dockerfile` --- .travis.yml | 3 +- Dockerfile | 8 +- README.rst | 9 +- codeclimate-radon | 6 +- docs/conf.py | 18 +- pyproject.toml | 5 +- radon/cli/__init__.py | 20 +- radon/cli/harvest.py | 18 +- radon/cli/tools.py | 250 ++++--------------------- radon/contrib/flake8.py | 2 +- radon/raw.py | 8 +- radon/tests/conftest.py | 2 +- radon/tests/data/__init__.py | 5 - radon/tests/data/no_encoding.py | 2 +- radon/tests/data/py3unicode.py | 3 - radon/tests/run.py | 9 +- radon/tests/test_cli.py | 7 +- radon/tests/test_cli_harvest.py | 17 +- radon/tests/test_cli_tools.py | 19 +- radon/tests/test_complexity_visitor.py | 7 - radon/visitors.py | 10 +- setup.py | 2 - test_requirements.txt | 3 +- tox.ini | 2 +- 24 files changed, 98 insertions(+), 337 deletions(-) delete mode 100644 radon/tests/data/__init__.py diff --git a/.travis.yml b/.travis.yml index a55d193..450ebca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python os: linux dist: xenial python: - - "2.7" - "3.6" - "3.7" - "3.8" @@ -10,7 +9,7 @@ python: - "pypy3.5" install: - pip install . - - pip install -r test_requirements.txt + - pip install --requirement test_requirements.txt script: - make tests - make cov diff --git a/Dockerfile b/Dockerfile index f42a133..77731c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,11 @@ FROM alpine:edge -MAINTAINER rubik +LABEL maintainer="rubik" WORKDIR /usr/src/app COPY . /usr/src/app RUN apk --update add \ - python2 python3 py2-pip && \ - pip2 install --upgrade pip && \ - pip2 install --requirement requirements.txt && \ - pip2 install . && \ - mv /usr/bin/radon /usr/bin/radon2 && \ + python3 py3-pip && \ pip3 install --requirement requirements.txt && \ pip3 install . && \ mv /usr/bin/radon /usr/bin/radon3 && \ diff --git a/README.rst b/README.rst index 9fafd9d..8ad1fee 100644 --- a/README.rst +++ b/README.rst @@ -30,10 +30,8 @@ Radon can compute: Requirements ------------ -Radon will run from **Python 2.7** to **Python 3.13** (except Python versions -from 3.0 to 3.3) with a single code base and without the need of tools like -2to3 or six. It can also run on **PyPy** without any problems (currently PyPy -3.5 v7.3.1 is used in tests). +Radon will run with **Python versions 3.3 to 3.13**; and through +**PyPy** (currently PyPy 3.5 v7.3.1 is used in tests). Radon depends on as few packages as possible. Currently only `mando` is strictly required (for the CLI interface). `colorama` is also listed as a @@ -41,8 +39,7 @@ dependency but if Radon cannot import it, the output simply will not be colored. **Note**: -**Python 2.6** was supported until version 1.5.0. Starting from version 2.0, it -is not supported anymore. +**Python2.7** support was dropped in v6.0.0. Installation ------------ diff --git a/codeclimate-radon b/codeclimate-radon index 656070e..5072d67 100755 --- a/codeclimate-radon +++ b/codeclimate-radon @@ -19,10 +19,8 @@ if os.path.exists("/config.json"): if config["config"].get("python_version"): version = config["config"].get("python_version") - if version == "2" or version == 2: - binstub = "radon2" - elif version != "3" and version != 3: - sys.exit("Invalid python_version; must be either 2 or 3") + if version != "3" and version != 3: + sys.exit("Invalid python_version; must be version 3") if config["config"].get("encoding"): encoding = config["config"].get("encoding") os.environ["RADONFILESENCODING"] = encoding diff --git a/docs/conf.py b/docs/conf.py index 2f1da1a..0b1be7e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Radon documentation build configuration file, created by # sphinx-quickstart on Thu Oct 11 16:08:21 2012. # @@ -46,9 +44,9 @@ master_doc = 'index' # General information about the project. -project = u'Radon' +project = 'Radon' build_date = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) -copyright = u'{0}, Michele Lacchia'.format('-'.join(map(str, +copyright = '{0}, Michele Lacchia'.format('-'.join(map(str, range(2012, build_date.year + 1)))) @@ -193,8 +191,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Radon.tex', u'Radon Documentation', - u'Michele Lacchia', 'manual'), + ('index', 'Radon.tex', 'Radon Documentation', + 'Michele Lacchia', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -223,8 +221,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'radon', u'Radon Documentation', - [u'Michele Lacchia'], 1) + ('index', 'radon', 'Radon Documentation', + ['Michele Lacchia'], 1) ] # If true, show URL addresses after external links. @@ -237,8 +235,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Radon', u'Radon Documentation', - u'Michele Lacchia', 'Radon', 'One line description of project.', + ('index', 'Radon', 'Radon Documentation', + 'Michele Lacchia', 'Radon', 'One line description of project.', 'Miscellaneous'), ] diff --git a/pyproject.toml b/pyproject.toml index 6d3d1ab..e28d9ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,10 +17,7 @@ colorama = [ [tool.poetry.group.dev.dependencies] coverage = "*" coveralls = "*" -pytest = [ - {version = ">=2.7", markers = "python_version < \"3.0\""}, - {version = ">5.0", markers = "python_version >= \"3.0\""} -] +pytest = ">5.0" pytest-mock = "*" argparse = "*" nbformat = "*" diff --git a/radon/cli/__init__.py b/radon/cli/__init__.py index 3af5138..2e163f5 100644 --- a/radon/cli/__init__.py +++ b/radon/cli/__init__.py @@ -1,5 +1,6 @@ '''In this module the CLI interface is created.''' +import configparser import inspect import os import sys @@ -27,16 +28,11 @@ RawHarvester, ) -if sys.version_info[0] == 2: - import ConfigParser as configparser -else: - import configparser - CONFIG_SECTION_NAME = 'radon' -class FileConfig(object): +class FileConfig: ''' Yield default options by reading local configuration files. ''' @@ -319,7 +315,7 @@ def hal( log_result(harvester, json=json, xml=False, md=False, stream=stream) -class Config(object): +class Config: '''An object holding config values.''' def __init__(self, **kwargs): @@ -347,12 +343,8 @@ def __eq__(self, other): @classmethod def from_function(cls, func): '''Construct a Config object from a function's defaults.''' - kwonlydefaults = {} - try: - argspec = inspect.getfullargspec(func) - kwonlydefaults = argspec.kwonlydefaults or {} - except AttributeError: # pragma: no cover - argspec = inspect.getargspec(func) + argspec = inspect.getfullargspec(func) + kwonlydefaults = argspec.kwonlydefaults or {} args, _, _, defaults = argspec[:4] values = dict(zip(reversed(args), reversed(defaults or []))) values.update(kwonlydefaults) @@ -420,7 +412,7 @@ def log_list(lst, *args, **kwargs): def log_error(msg, *args, **kwargs): '''Log an error message. Arguments are the same as log().''' - log('{0}{1}ERROR{2}: {3}'.format(BRIGHT, RED, RESET, msg), *args, **kwargs) + log('{}{}ERROR{}: {}'.format(BRIGHT, RED, RESET, msg), *args, **kwargs) @contextmanager diff --git a/radon/cli/harvest.py b/radon/cli/harvest.py index ac14fe5..82d346c 100644 --- a/radon/cli/harvest.py +++ b/radon/cli/harvest.py @@ -1,9 +1,8 @@ '''This module holds the base Harvester class and all its subclassess.''' import collections +import io import json -import sys -from builtins import super from radon.cli.colors import MI_RANKS, RANKS_COLORS, RESET from radon.cli.tools import ( @@ -26,11 +25,6 @@ from radon.metrics import h_visit, mi_rank, mi_visit from radon.raw import analyze -if sys.version_info[0] < 3: - from StringIO import StringIO -else: - from io import StringIO - try: import nbformat @@ -39,7 +33,7 @@ SUPPORTS_IPYNB = False -class Harvester(object): +class Harvester: '''Base class defining the interface of a Harvester object. A Harvester has the following lifecycle: @@ -108,7 +102,7 @@ def run(self): doc = "\n".join(cells) yield ( name, - self.gobble(StringIO(strip_ipython(doc))), + self.gobble(io.StringIO(strip_ipython(doc))), ) if self.config.ipynb_cells: @@ -116,9 +110,9 @@ def run(self): cellid = 0 for source in cells: yield ( - "{0}:[{1}]".format(name, cellid), + "{}:[{}]".format(name, cellid), self.gobble( - StringIO(strip_ipython(source)) + io.StringIO(strip_ipython(source)) ), ) cellid += 1 @@ -374,7 +368,7 @@ def to_terminal(self): color = MI_RANKS[rank] to_show = '' if self.config.show: - to_show = ' ({0:.2f})'.format(mi['mi']) + to_show = ' ({:.2f})'.format(mi['mi']) yield '{0} - {1}{2}{3}{4}', (name, color, rank, to_show, RESET), {} diff --git a/radon/cli/tools.py b/radon/cli/tools.py index cc8d4fa..7d14a7e 100644 --- a/radon/cli/tools.py +++ b/radon/cli/tools.py @@ -7,7 +7,6 @@ import fnmatch import hashlib import json -import locale import os import platform import re @@ -27,215 +26,46 @@ except ImportError: SUPPORTS_IPYNB = False -# PyPy doesn't support encoding parameter in `open()` function and works with -# UTF-8 encoding by default -if platform.python_implementation() == 'PyPy': - - @contextmanager - def _open(path): - '''Mock of the built-in `open()` function. If `path` is `-` then - `sys.stdin` is returned. - ''' - if path == '-': - yield sys.stdin - else: - with open(path) as f: - yield f - -else: - if sys.version_info[:2] >= (3, 0): - default_encoding = 'utf-8' +@contextmanager +def _open(path): + '''Mock of the built-in `open()` function. If `path` is `-` then + `sys.stdin` is returned. + ''' + if path == '-': + yield sys.stdin else: - default_encoding = locale.getpreferredencoding(False) - # Add customized file encoding to fix #86. - # By default `open()` function uses `locale.getpreferredencoding(False)` - # encoding (see https://docs.python.org/3/library/functions.html#open). - # This code allows to change `open()` encoding by setting an environment - # variable. - _encoding = os.getenv( - 'RADONFILESENCODING', default_encoding - ) - - if sys.version_info[:2] < (2, 7): - # This open function treats line-endings slightly differently than - # io.open. But the latter is implemented in pure Python in version 2.6, - # so we'll live with the differences instead of taking a hit on the - # speed. Radon does a lot of file reading, so the difference in speed - # is significant. - from codecs import open as _open_function - elif sys.version_info[:2] < (3, 0): - from codecs import BOM_UTF8, lookup - from io import TextIOWrapper - from io import open as _io_open_function - - cookie_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)') - blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)') - - def _get_normal_name(orig_enc): - '''Imitates get_normal_name in tokenizer.c.''' - # Only care about the first 12 characters. - enc = orig_enc[:12].lower().replace('_', '-') - if enc == 'utf-8' or enc.startswith('utf-8-'): - return 'utf-8' - if enc in ( - 'latin-1', - 'iso-8859-1', - 'iso-latin-1', - ) or enc.startswith(('latin-1-', 'iso-8859-1-', 'iso-latin-1-')): - return 'iso-8859-1' - return orig_enc - - def detect_encoding(readline): - ''' - The detect_encoding() function is used to detect the encoding that - should be used to decode a Python source file. It requires one - argument, readline, in the same way as the tokenize() generator. - - It will call readline a maximum of twice, and return the encoding - used (as a string) and a list of any lines (left as bytes) it has - read in. - - It detects the encoding from the presence of a utf-8 bom or an - encoding cookie as specified in pep-0263. If both a bom and a - cookie are present, but disagree, a SyntaxError will be raised. If - the encoding cookie is an invalid charset, raise a SyntaxError. - Note that if a utf-8 bom is found, 'utf-8-sig' is returned. - - If no encoding is specified, then the default of 'utf-8' will be - returned. The third argument indicates whether the encoding cookie - was found or not. - ''' - try: - filename = readline.__self__.name - except AttributeError: - filename = None - bom_found = False + # PyPy doesn't support encoding parameter in `open()` function and works with + # UTF-8 encoding by default + if platform.python_implementation() == 'PyPy': encoding = None - default = 'utf-8' - - def read_or_stop(): - try: - return readline() - except StopIteration: - return b'' - - def find_cookie(line): - try: - # Decode as UTF-8. Either the line is an encoding - # declaration, in which case it should be pure ASCII, or it - # must be UTF-8 per default encoding. - line_string = line.decode('utf-8') - except UnicodeDecodeError: - msg = 'invalid or missing encoding declaration' - if filename is not None: - msg = '{} for {!r}'.format(msg, filename) - raise SyntaxError(msg) - - match = cookie_re.match(line_string) - if not match: - return None - encoding = _get_normal_name(match.group(1)) - try: - lookup(encoding) - except LookupError: - # This behaviour mimics the Python interpreter - if filename is None: - msg = 'unknown encoding: ' + encoding - else: - msg = 'unknown encoding for {!r}: ' '{}'.format( - filename, encoding - ) - raise SyntaxError(msg) - - if bom_found: - if encoding != 'utf-8': - # This behaviour mimics the Python interpreter - if filename is None: - msg = 'encoding problem: utf-8' - else: - msg = 'encoding problem for ' '{!r}: utf-8'.format( - filename - ) - raise SyntaxError(msg) - encoding += '-sig' - return encoding - - first = read_or_stop() - if first.startswith(BOM_UTF8): - bom_found = True - first = first[3:] - default = 'utf-8-sig' - if not first: - return default, [], False - - encoding = find_cookie(first) - if encoding: - return encoding, [first], True - if not blank_re.match(first): - return default, [first], False - - second = read_or_stop() - if not second: - return default, [first], False - - encoding = find_cookie(second) - if encoding: - return encoding, [first, second], True - - return default, [first, second], False - - def _open_function(filename, encoding=None): - '''Open a file in read only mode using the encoding detected by - detect_encoding(). - ''' - # Note: Python 3 uses builtins.open here.. - buffer = _io_open_function(filename, 'rb') - try: - encoding, lines, found = detect_encoding(buffer.readline) - # Note: Python 3's tokenize does buffer seek(0), but that - # leaves the encoding cookie in the file and ast.parse - # does not like Unicode text with an encoding cookie. - # If the encoding was not found we seek to the start anyway - if found: - buffer.seek(sum(len(line) for line in lines)) - else: - buffer.seek(0) - text = TextIOWrapper(buffer, encoding, line_buffering=True) - text.mode = 'r' - return text - except Exception: - buffer.close() - raise - - else: - _open_function = open - - @contextmanager - def _open(path): - '''Mock of the built-in `open()` function. If `path` is `-` then - `sys.stdin` is returned. - ''' - if path == '-': - yield sys.stdin else: - with _open_function(path, encoding=_encoding) as f: - yield f + # Add customized file encoding to fix #86. + # By default `open()` function uses `locale.getpreferredencoding(False)` + # encoding (see https://docs.python.org/3/library/functions.html#open). + # This code allows to change `open()` encoding by setting an environment + # variable. + encoding = os.getenv('RADONFILESENCODING', 'utf-8') + + with open(path, encoding=encoding) as f: + yield f def _is_python_file(filename): '''Check if a file is a Python source file.''' - if ( - filename == '-' - or filename.endswith('.py') - or (SUPPORTS_IPYNB and filename.endswith('.ipynb')) + if any( + ( + filename == '-', + filename.endswith('.py'), + (SUPPORTS_IPYNB and filename.endswith('.ipynb')) + ) ): return True try: with open(filename) as fobj: first_line = fobj.readline() - if first_line.startswith('#!') and 'python' in first_line: - return True + if first_line.startswith('#!') and 'python' in first_line: + return True except Exception: return False return False @@ -253,14 +83,16 @@ def iter_filenames(paths, exclude=None, ignore=None): yield '-' return exclude = exclude.split(',') if exclude else [] - ignore = '.*,{0}'.format(ignore).split(',') if ignore else ['.*'] + ignore = '.*,{}'.format(ignore).split(',') if ignore else ['.*'] for path in paths: - if ( - os.path.isfile(path) - and _is_python_file(path) - and ( - not exclude - or not any(fnmatch.fnmatch(path, p) for p in exclude) + if all( + ( + os.path.isfile(path), + _is_python_file(path), + ( + not exclude + or not any(fnmatch.fnmatch(path, p) for p in exclude) + ) ) ): yield path @@ -338,7 +170,7 @@ def dict_to_xml(results): unit = et.SubElement(metric, 'unit') name = block['name'] if 'classname' in block: - name = '{0}.{1}'.format(block['classname'], block['name']) + name = '{}.{}'.format(block['classname'], block['name']) unit.text = name et.SubElement(metric, 'classification').text = block['rank'] @@ -387,7 +219,7 @@ def dict_to_codeclimate_issues(results, threshold='B'): for path in results: info = results[path] if type(info) is dict and info.get('error'): - description = 'Error: {0}'.format(info.get('error', error_content)) + description = 'Error: {}'.format(info.get('error', error_content)) beginline = re.search(r'\d+', description) error_category = 'Bug Risk' @@ -418,8 +250,8 @@ def dict_to_codeclimate_issues(results, threshold='B'): complexity = offender['complexity'] category = 'Complexity' description = ( - 'Cyclomatic complexity is too high in {0} {1}. ' - '({2})'.format( + 'Cyclomatic complexity is too high in {} {}. ' + '({})'.format( offender['type'], offender['name'], complexity ) ) @@ -487,7 +319,7 @@ def _format_line(block, ranked, show_complexity=False): ''' letter_colored = LETTERS_COLORS[block.letter] + block.letter rank_colored = RANKS_COLORS[ranked] + ranked - compl = '' if not show_complexity else ' ({0})'.format(block.complexity) + compl = '' if not show_complexity else ' ({})'.format(block.complexity) return TEMPLATE.format( BRIGHT, letter_colored, diff --git a/radon/contrib/flake8.py b/radon/contrib/flake8.py index 2376f4d..d1fb575 100644 --- a/radon/contrib/flake8.py +++ b/radon/contrib/flake8.py @@ -2,7 +2,7 @@ from radon.visitors import ComplexityVisitor -class Flake8Checker(object): +class Flake8Checker: '''Entry point for the Flake8 tool.''' name = 'radon' diff --git a/radon/raw.py b/radon/raw.py index 1ac2bc1..a27d8de 100644 --- a/radon/raw.py +++ b/radon/raw.py @@ -5,14 +5,10 @@ ''' import collections +import io import operator import tokenize -try: - import StringIO as io -except ImportError: # pragma: no cover - import io - __all__ = [ 'OP', @@ -209,7 +205,7 @@ def analyze(source): # lines tokens, parsed_lines = _get_all_tokens(line, lines) except StopIteration: - raise SyntaxError('SyntaxError at line: {0}'.format(lineno)) + raise SyntaxError('SyntaxError at line: {}'.format(lineno)) lineno += len(parsed_lines) diff --git a/radon/tests/conftest.py b/radon/tests/conftest.py index a65e5e4..ca83b32 100644 --- a/radon/tests/conftest.py +++ b/radon/tests/conftest.py @@ -9,7 +9,7 @@ def log_mock(mocker): return mocker.patch('radon.cli.log_result') -class RadonConfig(object): +class RadonConfig: def __init__(self): self._fname = os.path.join(os.path.dirname(__file__), 'radon.cfg') diff --git a/radon/tests/data/__init__.py b/radon/tests/data/__init__.py deleted file mode 100644 index c9a1173..0000000 --- a/radon/tests/data/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- - - -def fun(arg): - a = 'èèèè' diff --git a/radon/tests/data/no_encoding.py b/radon/tests/data/no_encoding.py index 0b910b8..100bc63 100644 --- a/radon/tests/data/no_encoding.py +++ b/radon/tests/data/no_encoding.py @@ -1,2 +1,2 @@ -class Foo(object): +class Foo: pass diff --git a/radon/tests/data/py3unicode.py b/radon/tests/data/py3unicode.py index e20eb33..37d9ee2 100644 --- a/radon/tests/data/py3unicode.py +++ b/radon/tests/data/py3unicode.py @@ -1,5 +1,2 @@ -# -*- coding: utf-8 -*- - - def èèèè(òòòò): pass diff --git a/radon/tests/run.py b/radon/tests/run.py index 106b4b7..e5a9b23 100644 --- a/radon/tests/run.py +++ b/radon/tests/run.py @@ -2,14 +2,7 @@ import sys import pytest - # see: https://docs.pytest.org/en/6.2.x/deprecations.html#the-strict-command-line-option - # This check can be removed once Python 2.x support is dropped as the new - # pytest option (--strict-markers) is available in pytest for all Python 3.x - from packaging import version - if version.parse(pytest.__version__) < version.parse('6.2'): - pytest_args = ['--strict'] - else: - pytest_args = ['--strict-markers'] + pytest_args = ['--strict-markers'] ret = pytest.main(pytest_args) sys.exit(ret) diff --git a/radon/tests/test_cli.py b/radon/tests/test_cli.py index 8b19372..d79ab17 100644 --- a/radon/tests/test_cli.py +++ b/radon/tests/test_cli.py @@ -69,7 +69,7 @@ def test_config_for(): def test_config_converts_types(mocker): test_config = ConfigParser() test_config.read_string( - u''' + ''' [radon] str_test = B int_test = 19 @@ -175,10 +175,7 @@ def test_encoding(mocker, log_mock): RawHarvester: raw_cfg, CCHarvester: CC_CONFIG, } - if sys.version_info[0] < 3: - target = 'data/__init__.py' - else: - target = 'data/py3unicode.py' + target = 'data/py3unicode.py' fnames = [ os.path.join(DIRNAME, target), # This one will fail if detect_encoding() removes the first lines diff --git a/radon/tests/test_cli_harvest.py b/radon/tests/test_cli_harvest.py index a7b451f..cd68e94 100644 --- a/radon/tests/test_cli_harvest.py +++ b/radon/tests/test_cli_harvest.py @@ -1,7 +1,4 @@ -try: - import collections.abc as collections_abc -except ImportError: - import collections as collections_abc +import collections import pytest @@ -43,7 +40,7 @@ def fake_gobble_raising(fobj): def fake_run(): for i in range(3): - yield {'file-{0}'.format(i): i ** 2} + yield {'file-{}'.format(i): i ** 2} @pytest.fixture @@ -101,7 +98,7 @@ def test_base_to_terminal_not_implemented(base_config): def test_base_run(base_config): h = harvest.Harvester(['-'], base_config) h.gobble = fake_gobble - assert isinstance(h.run(), collections_abc.Iterator) + assert isinstance(h.run(), collections.abc.Iterator) assert list(h.run()) == [('-', 42)] h.gobble = fake_gobble_raising assert list(h.run()) == [('-', {'error': 'mystr'})] @@ -111,10 +108,10 @@ def test_base_results(base_config): h = harvest.Harvester([], base_config) h.run = fake_run results = h.results - assert isinstance(results, collections_abc.Iterator) + assert isinstance(results, collections.abc.Iterator) assert list(results) == [{'file-0': 0}, {'file-1': 1}, {'file-2': 4}] - assert not isinstance(h.results, collections_abc.Iterator) - assert isinstance(h.results, collections_abc.Iterable) + assert not isinstance(h.results, collections.abc.Iterator) + assert isinstance(h.results, collections.abc.Iterable) assert isinstance(h.results, list) @@ -374,7 +371,7 @@ def test_mi_as_xml(mi_config): def test_mi_to_terminal(mi_config, mocker): reset_mock = mocker.patch('radon.cli.harvest.RESET') ranks_mock = mocker.patch('radon.cli.harvest.MI_RANKS') - ranks_mock.__getitem__.side_effect = lambda j: '<|{0}|>'.format(j) + ranks_mock.__getitem__.side_effect = lambda j: '<|{}|>'.format(j) reset_mock.__eq__.side_effect = lambda o: o == '__R__' h = harvest.MIHarvester([], mi_config) diff --git a/radon/tests/test_cli_tools.py b/radon/tests/test_cli_tools.py index 5bdd2b4..847655d 100644 --- a/radon/tests/test_cli_tools.py +++ b/radon/tests/test_cli_tools.py @@ -1,5 +1,4 @@ import json -import locale import os import platform import sys @@ -25,7 +24,7 @@ def fake_walk(start): } yield '.', dirs, ['tox.ini', 'amod.py', 'test_all.py', 'fake.yp', 'noext'] for d in dirs: - yield './{0}'.format(d), [], contents[d] + yield './{}'.format(d), [], contents[d] def fake_is_python_file(filename): @@ -54,15 +53,9 @@ def test_open(mocker): tools._open('randomfile.py').__enter__() m.assert_called_with('randomfile.py') else: - mocker.patch('radon.cli.tools._open_function', m, create=True) + mocker.patch('radon.cli.tools.open', m, create=True) tools._open('randomfile.py').__enter__() - if sys.version_info[:2] >= (3, 0): - default_encoding = 'utf-8' - else: - default_encoding = locale.getpreferredencoding(False) - except_encoding = os.getenv( - 'RADONFILESENCODING', default_encoding - ) + except_encoding = os.getenv('RADONFILESENCODING', 'utf-8') m.assert_called_with('randomfile.py', encoding=except_encoding) @@ -563,8 +556,8 @@ def test_cc_to_codeclimate(): def test_cc_to_terminal(): # do the patching - tools.LETTERS_COLORS = dict((l, ''.format(l)) for l in 'FMC') - tools.RANKS_COLORS = dict((r, '<|{0}|>'.format(r)) for r in 'ABCDEF') + tools.LETTERS_COLORS = dict((l, ''.format(l)) for l in 'FMC') + tools.RANKS_COLORS = dict((r, '<|{}|>'.format(r)) for r in 'ABCDEF') tools.BRIGHT = '@' tools.RESET = '__R__' @@ -577,7 +570,7 @@ def test_cc_to_terminal(): '@F __R__12:0 f3 - <|E|>E (32)__R__', '@F __R__12:0 f4 - <|F|>F (41)__R__', ] - res_noshow = ['{0}__R__'.format(r[: r.index('(') - 1]) for r in res] + res_noshow = ['{}__R__'.format(r[: r.index('(') - 1]) for r in res] assert tools.cc_to_terminal(results, False, 'A', 'F', False) == ( res_noshow, diff --git a/radon/tests/test_complexity_visitor.py b/radon/tests/test_complexity_visitor.py index aabd842..77599a6 100644 --- a/radon/tests/test_complexity_visitor.py +++ b/radon/tests/test_complexity_visitor.py @@ -331,11 +331,6 @@ def test_yo(self): 1, {'no_assert': True}, ), -] - - -# These run only if Python version is >= 2.7 -ADDITIONAL_BLOCKS = [ ( ''' {i for i in range(4)} @@ -407,8 +402,6 @@ def test_yo(self): ] BLOCKS = SIMPLE_BLOCKS[:] -if sys.version_info[:2] >= (2, 7): - BLOCKS.extend(ADDITIONAL_BLOCKS) if sys.version_info[:2] >= (3, 10): BLOCKS.extend(MATCH_STATEMENT_BLOCKS) diff --git a/radon/visitors.py b/radon/visitors.py index e774648..acbf3c0 100644 --- a/radon/visitors.py +++ b/radon/visitors.py @@ -67,11 +67,11 @@ def fullname(self): ''' if self.classname is None: return self.name - return '{0}.{1}'.format(self.classname, self.name) + return '{}.{}'.format(self.classname, self.name) def __str__(self): '''String representation of a function block.''' - return '{0} {1}:{2}->{3} {4} - {5}'.format( + return '{} {}:{}->{} {} - {}'.format( self.letter, self.lineno, self.col_offset, @@ -105,7 +105,7 @@ def complexity(self): def __str__(self): '''String representation of a class block.''' - return '{0} {1}:{2}->{3} {4} - {5}'.format( + return '{} {}:{}->{} {} - {}'.format( self.letter, self.lineno, self.col_offset, @@ -251,7 +251,7 @@ def generic_visit(self, node): elif name == 'comprehension': self.complexity += len(node.ifs) + 1 - super(ComplexityVisitor, self).generic_visit(node) + super().generic_visit(node) def visit_Assert(self, node): '''When visiting `assert` statements, the complexity is increased only @@ -397,7 +397,7 @@ def aux(self, node): self.operands_seen.add((self.context, new_operand)) # Now dispatch to children - super(HalsteadVisitor, self).generic_visit(node) + super().generic_visit(node) return aux diff --git a/setup.py b/setup.py index 79cffeb..6dfebc3 100644 --- a/setup.py +++ b/setup.py @@ -46,8 +46,6 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', diff --git a/test_requirements.txt b/test_requirements.txt index 1994134..2c0cd57 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,6 +1,5 @@ coverage coveralls -pytest>=5.0; python_version >= '3.0' -pytest>=2.7; python_version < '3.0' +pytest>=5.0 pytest-mock nbformat diff --git a/tox.ini b/tox.ini index 5781ba4..95117f6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py33,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313,pypy +envlist = py33,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313,pypy [testenv] deps = -r{toxinidir}/test_requirements.txt