Skip to content
This repository has been archived by the owner on Nov 3, 2023. It is now read-only.

Commit

Permalink
Drop support for Python 3.5 (#510)
Browse files Browse the repository at this point in the history
* Drop support for Python 3.5

* Upgrade Python syntax with pyupgrade --py36-plus

* Drop support for Python 3.5

* Remove D302, redundant now Python 2 is dropped
  • Loading branch information
hugovk committed Aug 31, 2020
1 parent 17a1c64 commit 408f962
Show file tree
Hide file tree
Showing 18 changed files with 36 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: [3.5, 3.6, 3.7, 3.8, 3.9-dev]
python-version: [3.6, 3.7, 3.8, 3.9-dev]

steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Expand Up @@ -22,7 +22,7 @@ docstring conventions.
`PEP 257 <http://www.python.org/dev/peps/pep-0257/>`_ out of the box, but it
should not be considered a reference implementation.

**pydocstyle** supports Python 3.5, 3.6, 3.7 and 3.8.
**pydocstyle** supports Python 3.6, 3.7 and 3.8.


Quick Start
Expand Down
2 changes: 0 additions & 2 deletions docs/conf.py
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# pydocstyle documentation build configuration file, created by
# sphinx-quickstart on Fri Jan 30 20:30:42 2015.
#
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Expand Up @@ -8,7 +8,7 @@ docstring conventions.
`PEP 257 <http://www.python.org/dev/peps/pep-0257/>`_ out of the box, but it
should not be considered a reference implementation.

**pydocstyle** supports Python 3.5, 3.6, 3.7 and 3.8.
**pydocstyle** supports Python 3.6, 3.7 and 3.8.


.. include:: quickstart.rst
Expand Down
4 changes: 4 additions & 0 deletions docs/release_notes.rst
Expand Up @@ -8,6 +8,10 @@ Release Notes
Current Development Version
---------------------------

Major Updates

* Support for Python 3.5 has been dropped (#510).

New Features

* Add flag to disable `# noqa` comment processing in API (#485).
Expand Down
7 changes: 3 additions & 4 deletions setup.py
@@ -1,5 +1,4 @@
from setuptools import setup
import sys

# Do not update the version manually - it is managed by `bumpversion`.
version = '5.1.2rc'
Expand All @@ -24,14 +23,14 @@
'Environment :: Console',
'Development Status :: 5 - Production/Stable',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3 :: Only',
'Operating System :: OS Independent',
'License :: OSI Approved :: MIT License',
],
python_requires='>=3.5',
python_requires='>=3.6',
keywords='pydocstyle, PEP 257, pep257, PEP 8, pep8, docstrings',
packages=('pydocstyle',),
package_dir={'': 'src'},
Expand All @@ -43,6 +42,6 @@
],
},
project_urls={
'Release Notes': 'http://www.pydocstyle.org/en/latest/release_notes.html',
'Release Notes': 'https://www.pydocstyle.org/en/latest/release_notes.html',
},
)
27 changes: 2 additions & 25 deletions src/pydocstyle/checker.py
Expand Up @@ -385,23 +385,6 @@ def check_backslashes(self, definition, docstring):
and not docstring.startswith(('r', 'ur'))):
return violations.D301()

@check_for(Definition)
def check_unicode_docstring(self, definition, docstring):
r'''D302: Use u""" for docstrings with Unicode.
For Unicode docstrings, use u"""Unicode triple-quoted strings""".
'''
if 'unicode_literals' in definition.module.future_imports:
return

# Just check that docstring is unicode, check_triple_double_quotes
# ensures the correct quotes.
if docstring and sys.version_info[0] <= 2:
if not is_ascii(docstring) and not docstring.startswith(
('u', 'ur')):
return violations.D302()

@staticmethod
def _check_ends_with(docstring, chars, violation):
"""First line ends with one of `chars`.
Expand Down Expand Up @@ -453,13 +436,7 @@ def check_imperative_mood(self, function, docstring): # def context
if check_word in IMPERATIVE_BLACKLIST:
return violations.D401b(first_word)

try:
correct_forms = IMPERATIVE_VERBS.get(stem(check_word))
except UnicodeDecodeError:
# This is raised when the docstring contains unicode
# characters in the first word, but is not a unicode
# string. In which case D302 will be reported. Ignoring.
return
correct_forms = IMPERATIVE_VERBS.get(stem(check_word))

if correct_forms and check_word not in correct_forms:
best = max(
Expand Down Expand Up @@ -985,7 +962,7 @@ def check(filenames, select=None, ignore=None, ignore_decorators=None, ignore_in
code = getattr(error, 'code', None)
if code in checked_codes:
yield error
except (EnvironmentError, AllError, ParseError) as error:
except (OSError, AllError, ParseError) as error:
log.warning('Error in file %s: %s', filename, error)
yield error
except tk.TokenError:
Expand Down
8 changes: 4 additions & 4 deletions src/pydocstyle/config.py
Expand Up @@ -316,7 +316,7 @@ def _read_configuration_file(self, path):
continue

if opt.replace('_', '-') not in self.CONFIG_FILE_OPTIONS:
log.warning("Unknown option '{}' ignored".format(opt))
log.warning(f"Unknown option '{opt}' ignored")
continue

normalized_opt = opt.replace('-', '_')
Expand All @@ -335,7 +335,7 @@ def _read_configuration_file(self, path):

if options is not None:
if not self._validate_options(options):
raise IllegalConfiguration('in file: {}'.format(path))
raise IllegalConfiguration(f'in file: {path}')

return options, should_inherit

Expand Down Expand Up @@ -388,7 +388,7 @@ def _create_check_config(cls, options, use_defaults=True):

kwargs = dict(checked_codes=checked_codes)
for key in ('match', 'match_dir', 'ignore_decorators'):
kwargs[key] = getattr(cls, 'DEFAULT_{}_RE'.format(key.upper())) \
kwargs[key] = getattr(cls, f'DEFAULT_{key.upper()}_RE') \
if getattr(options, key) is None and use_defaults \
else getattr(options, key)
return CheckConfiguration(**kwargs)
Expand Down Expand Up @@ -521,7 +521,7 @@ def _get_set(value_str):
"""
return cls._expand_error_codes(
set([x.strip() for x in value_str.split(",")]) - {""}
{x.strip() for x in value_str.split(",")} - {""}
)

for opt in optional_set_options:
Expand Down
13 changes: 6 additions & 7 deletions src/pydocstyle/parser.py
Expand Up @@ -55,7 +55,7 @@ def __eq__(self, other):
def __repr__(self):
kwargs = ', '.join('{}={!r}'.format(field, getattr(self, field))
for field in self._fields)
return '{}({})'.format(self.__class__.__name__, kwargs)
return f'{self.__class__.__name__}({kwargs})'


class Definition(Value):
Expand Down Expand Up @@ -97,9 +97,9 @@ def is_empty_or_comment(line):
return ''.join(reversed(list(filtered_src)))

def __str__(self):
out = 'in {} {} `{}`'.format(self._publicity, self._human, self.name)
out = f'in {self._publicity} {self._human} `{self.name}`'
if self.skipped_error_codes:
out += ' (skipping {})'.format(self.skipped_error_codes)
out += f' (skipping {self.skipped_error_codes})'
return out


Expand Down Expand Up @@ -211,7 +211,7 @@ def is_public(self):
# Check if we are a setter/deleter method, and mark as private if so.
for decorator in self.decorators:
# Given 'foo', match 'foo.bar' but not 'foobar' or 'sfoo'
if re(r"^{}\.".format(self.name)).match(decorator.name):
if re(fr"^{self.name}\.").match(decorator.name):
return False
name_is_public = (not self.name.startswith('_') or
self.name in VARIADIC_MAGIC_METHODS or
Expand Down Expand Up @@ -343,7 +343,7 @@ def __init__(self, *args):
self.kind = TokenKind(self.kind)

def __str__(self):
return "{!r} ({})".format(self.kind, self.value)
return f"{self.kind!r} ({self.value})"


class Parser:
Expand Down Expand Up @@ -472,8 +472,7 @@ def parse_definitions(self, class_, dunder_all=False):
yield self.parse_definition(class_._nest(self.current.value))
elif self.current.kind == tk.INDENT:
self.consume(tk.INDENT)
for definition in self.parse_definitions(class_):
yield definition
yield from self.parse_definitions(class_)
elif self.current.kind == tk.DEDENT:
self.consume(tk.DEDENT)
return
Expand Down
10 changes: 5 additions & 5 deletions src/pydocstyle/violations.py
Expand Up @@ -52,10 +52,10 @@ def set_context(self, definition: Definition, explanation: str) -> None:
@property
def message(self) -> str:
"""Return the message to print to the user."""
ret = '{}: {}'.format(self.code, self.short_desc)
ret = f'{self.code}: {self.short_desc}'
if self.context is not None:
specific_error_msg = self.context.format(*self.parameters)
ret += ' ({})'.format(specific_error_msg)
ret += f' ({specific_error_msg})'
return ret

@property
Expand All @@ -69,7 +69,7 @@ def lines(self) -> str:
lines_stripped = list(reversed(list(dropwhile(is_blank,
reversed(lines)))))
numbers_width = len(str(offset + len(lines_stripped)))
line_format = '{{:{}}}:{{}}'.format(numbers_width)
line_format = f'{{:{numbers_width}}}:{{}}'
for n, line in enumerate(lines_stripped):
if line:
line = ' ' + line
Expand Down Expand Up @@ -159,7 +159,7 @@ def to_rst(cls) -> str:
for group in cls.groups:
table += sep_line
table += blank_line
table += '|' + '**{}**'.format(group.name).center(max_len + 9) + '|\n'
table += '|' + f'**{group.name}**'.center(max_len + 9) + '|\n'
table += blank_line
for error in group.errors:
table += sep_line
Expand Down Expand Up @@ -214,7 +214,7 @@ def to_rst(cls) -> str:
D300 = D3xx.create_error('D300', 'Use """triple double quotes"""',
'found {0}-quotes')
D301 = D3xx.create_error('D301', 'Use r""" if any backslashes in a docstring')
D302 = D3xx.create_error('D302', 'Use u""" for Unicode docstrings')
D302 = D3xx.create_error('D302', 'Deprecated: Use u""" for Unicode docstrings')

D4xx = ErrorRegistry.create_group('D4', 'Docstring Content Issues')
D400 = D4xx.create_error('D400', 'First line should end with a period',
Expand Down
8 changes: 0 additions & 8 deletions src/tests/parser_test.py
Expand Up @@ -52,10 +52,6 @@ def do_something(pos_param0, pos_param1, kw_param0="default"):

def test_simple_fstring():
"""Test parsing of a function with a simple fstring as a docstring."""
# fstrings are not supported in Python 3.5
if sys.version_info[0:2] == (3, 5):
return

parser = Parser()
code = CodeSnippet("""\
def do_something(pos_param0, pos_param1, kw_param0="default"):
Expand Down Expand Up @@ -85,10 +81,6 @@ def do_something(pos_param0, pos_param1, kw_param0="default"):

def test_fstring_with_args():
"""Test parsing of a function with an fstring with args as a docstring."""
# fstrings are not supported in Python 3.5
if sys.version_info[0:2] == (3, 5):
return

parser = Parser()
code = CodeSnippet("""\
foo = "bar"
Expand Down
2 changes: 1 addition & 1 deletion src/tests/test_cases/canonical_google_examples.py
Expand Up @@ -83,7 +83,7 @@ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
"('Attributes', not 'Attributes:')")
@expect("D407: Missing dashed underline after section ('Attributes')")
@expect("D413: Missing blank line after last section ('Attributes')")
class SampleClass(object):
class SampleClass:
"""Summary of class here.
Longer class information....
Expand Down
1 change: 0 additions & 1 deletion src/tests/test_cases/noqa.py
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# noqa: D400,D415
"""Test case for "# noqa" comments"""
from .expected import Expectation
Expand Down
11 changes: 0 additions & 11 deletions src/tests/test_cases/test.py
@@ -1,4 +1,3 @@
# encoding: utf-8
# No docstring, so we can test D100
from functools import wraps
import os
Expand Down Expand Up @@ -280,16 +279,6 @@ def exceptions_of_D301():
"""


if sys.version_info[0] <= 2:
@expect('D302: Use u""" for Unicode docstrings')
def unicode_unmarked():
"""Юникод."""

@expect('D302: Use u""" for Unicode docstrings')
def first_word_has_unicode_byte():
"""あy."""


@expect("D400: First line should end with a period (not 'y')")
@expect("D415: First line should end with a period, question mark, "
"or exclamation point (not 'y')")
Expand Down
2 changes: 0 additions & 2 deletions src/tests/test_cases/unicode_literals.py
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
"""A module."""

from __future__ import unicode_literals
from .expected import Expectation


Expand Down
2 changes: 1 addition & 1 deletion src/tests/test_definitions.py
Expand Up @@ -26,7 +26,7 @@
])
def test_complex_file(test_case):
"""Run domain-specific tests from test.py file."""
case_module = __import__('test_cases.{}'.format(test_case),
case_module = __import__(f'test_cases.{test_case}',
globals=globals(),
locals=locals(),
fromlist=['expectation'],
Expand Down
7 changes: 2 additions & 5 deletions src/tests/test_integration.py
@@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-

"""Use tox or py.test to run the test-suite."""

from __future__ import with_statement
from collections import namedtuple

import os
Expand Down Expand Up @@ -52,7 +49,7 @@ def write_config(self, prefix='', name='tox.ini', **kwargs):
self.makedirs(base)

with open(os.path.join(base, name), 'wt') as conf:
conf.write("[{}]\n".format(self.script_name))
conf.write(f"[{self.script_name}]\n")
for k, v in kwargs.items():
conf.write("{} = {}\n".format(k.replace('_', '-'), v))

Expand Down Expand Up @@ -284,7 +281,7 @@ def test_sectionless_config_file(env):
conf.write('[pdcstl]')
config_path = conf.name

_, err, code = env.invoke('--config={}'.format(config_path))
_, err, code = env.invoke(f'--config={config_path}')
assert code == 0
assert 'Configuration file does not contain a pydocstyle section' in err

Expand Down
10 changes: 5 additions & 5 deletions tox.ini
Expand Up @@ -4,7 +4,7 @@
# install tox" and then run "tox" from this directory.

[tox]
envlist = {py35,py36,py37,py38,py39}-{tests,install},docs,install,py36-docs
envlist = {py36,py37,py38,py39}-{tests,install},docs,install,py36-docs

[testenv]
download = true
Expand Down Expand Up @@ -42,10 +42,6 @@ commands = {[testenv:docs]commands}
# There's no way to generate sub-sections in tox.
# The following sections are all references to the `py37-install`.

[testenv:py35-install]
skip_install = {[testenv:install]skip_install}
commands = {[testenv:install]commands}

[testenv:py36-install]
skip_install = {[testenv:install]skip_install}
commands = {[testenv:install]commands}
Expand All @@ -58,6 +54,10 @@ commands = {[testenv:install]commands}
skip_install = {[testenv:install]skip_install}
commands = {[testenv:install]commands}

[testenv:py39-install]
skip_install = {[testenv:install]skip_install}
commands = {[testenv:install]commands}

[pytest]
pep8ignore =
test.py E701 E704
Expand Down

0 comments on commit 408f962

Please sign in to comment.