diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 91d87f6c..3e62215c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -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
diff --git a/README.rst b/README.rst
index 088b5f01..179345d4 100644
--- a/README.rst
+++ b/README.rst
@@ -22,7 +22,7 @@ docstring conventions.
`PEP 257 `_ 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
diff --git a/docs/conf.py b/docs/conf.py
index 05583dac..6f6412a5 100644
--- a/docs/conf.py
+++ b/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.
#
diff --git a/docs/index.rst b/docs/index.rst
index dfe636ed..991a01e4 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -8,7 +8,7 @@ docstring conventions.
`PEP 257 `_ 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
diff --git a/docs/release_notes.rst b/docs/release_notes.rst
index 45cf63af..52c55e16 100644
--- a/docs/release_notes.rst
+++ b/docs/release_notes.rst
@@ -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).
diff --git a/setup.py b/setup.py
index 80369721..501afe3e 100644
--- a/setup.py
+++ b/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'
@@ -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'},
@@ -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',
},
)
diff --git a/src/pydocstyle/checker.py b/src/pydocstyle/checker.py
index 3cabc9a9..294a8ce2 100644
--- a/src/pydocstyle/checker.py
+++ b/src/pydocstyle/checker.py
@@ -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`.
@@ -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(
@@ -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:
diff --git a/src/pydocstyle/config.py b/src/pydocstyle/config.py
index 60d11f00..6e789d92 100644
--- a/src/pydocstyle/config.py
+++ b/src/pydocstyle/config.py
@@ -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('-', '_')
@@ -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
@@ -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)
@@ -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:
diff --git a/src/pydocstyle/parser.py b/src/pydocstyle/parser.py
index 1b17c36c..a6afa863 100644
--- a/src/pydocstyle/parser.py
+++ b/src/pydocstyle/parser.py
@@ -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):
@@ -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
@@ -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
@@ -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:
@@ -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
diff --git a/src/pydocstyle/violations.py b/src/pydocstyle/violations.py
index 6515d3d6..f9f02f1e 100644
--- a/src/pydocstyle/violations.py
+++ b/src/pydocstyle/violations.py
@@ -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
@@ -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
@@ -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
@@ -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',
diff --git a/src/tests/parser_test.py b/src/tests/parser_test.py
index 05a329ec..061a3919 100644
--- a/src/tests/parser_test.py
+++ b/src/tests/parser_test.py
@@ -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"):
@@ -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"
diff --git a/src/tests/test_cases/canonical_google_examples.py b/src/tests/test_cases/canonical_google_examples.py
index c219191f..301f83c4 100644
--- a/src/tests/test_cases/canonical_google_examples.py
+++ b/src/tests/test_cases/canonical_google_examples.py
@@ -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....
diff --git a/src/tests/test_cases/noqa.py b/src/tests/test_cases/noqa.py
index 7eeeac87..bbbc6a41 100644
--- a/src/tests/test_cases/noqa.py
+++ b/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
diff --git a/src/tests/test_cases/test.py b/src/tests/test_cases/test.py
index 20b81fce..3d671e59 100644
--- a/src/tests/test_cases/test.py
+++ b/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
@@ -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')")
diff --git a/src/tests/test_cases/unicode_literals.py b/src/tests/test_cases/unicode_literals.py
index 5f7dd700..f0aa466e 100644
--- a/src/tests/test_cases/unicode_literals.py
+++ b/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
diff --git a/src/tests/test_definitions.py b/src/tests/test_definitions.py
index 83109b5a..b2bd0081 100644
--- a/src/tests/test_definitions.py
+++ b/src/tests/test_definitions.py
@@ -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'],
diff --git a/src/tests/test_integration.py b/src/tests/test_integration.py
index c5375c79..7de3b755 100644
--- a/src/tests/test_integration.py
+++ b/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
@@ -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))
@@ -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
diff --git a/tox.ini b/tox.ini
index 7d85e7ed..e6d08656 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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
@@ -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}
@@ -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