Skip to content

Commit

Permalink
Lint with Mypy, Pylint
Browse files Browse the repository at this point in the history
Drops Pypy and Python 2 support, since neither allows type annotations
  • Loading branch information
langston-barrett committed Feb 9, 2021
1 parent b987964 commit bce41f9
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.egg-info/
mypy_cache/
*.pyc
.*.swp
.cache/
Expand Down
55 changes: 55 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[MASTER]

ignore=.git
jobs=0
suggestion-mode=yes

[MESSAGES CONTROL]

disable=all
enable=assign-to-new-keyword,
bad-format-character,
bad-format-string-key,
bad-open-mode,
bad-string-format-type,
consider-using-get,
consider-using-join,
empty-docstring,
function-redefined,
not-in-loop,
redefined-builtin,
return-in-init,
too-few-format-args,
too-many-format-args,
truncated-format-string,
unnecessary-comprehension,
unused-argument,
unreachable,
unused-format-string-argument,
unused-format-string-key,
unused-import,
unused-variable,
using-constant-test

[REPORTS]

output-format=text
reports=no
score=no

[BASIC]

argument-naming-style=snake_case
attr-naming-style=snake_case
class-naming-style=PascalCase
const-naming-style=UPPER_CASE
function-naming-style=snake_case
include-naming-hint=yes
inlinevar-naming-style=snake_case
method-naming-style=snake_case
module-naming-style=snake_case
variable-naming-style=snake_case

[VARIABLES]

redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
11 changes: 11 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[mypy]
# TODO: Enable these
# disallow_any_generics = True
# disallow_incomplete_defs = True
# disallow_untyped_defs = True
show_error_codes = True
ignore_missing_imports = True
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unused_ignores = True
73 changes: 43 additions & 30 deletions sphinxcontrib/autoprogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
:license: BSD, see LICENSE for details.
"""
from __future__ import annotations

# pylint: disable=protected-access,missing-docstring
import argparse
import collections
import inspect
import os
import re
import sys
from typing import Any, Dict, Iterable, List, Optional, Tuple
import unittest

from docutils import nodes
Expand All @@ -37,7 +40,9 @@
)


def get_subparser_action(parser):
def get_subparser_action(
parser: argparse.ArgumentParser,
) -> Optional[argparse._SubParsersAction]:
neg1_action = parser._actions[-1]

if isinstance(neg1_action, argparse._SubParsersAction):
Expand All @@ -47,8 +52,16 @@ def get_subparser_action(parser):
if isinstance(a, argparse._SubParsersAction):
return a

return None


def scan_programs(parser, command=[], maxdepth=0, depth=0, groups=False):
def scan_programs(
parser: argparse.ArgumentParser,
command=[],
maxdepth: int = 0,
depth: int = 0,
groups: bool = False,
):
if maxdepth and depth >= maxdepth:
return

Expand All @@ -63,7 +76,7 @@ def scan_programs(parser, command=[], maxdepth=0, depth=0, groups=False):
yield command, options, parser

if parser._subparsers:
choices = ()
choices: Iterable[Tuple[Any, Any]] = ()

subp_action = get_subparser_action(parser)

Expand Down Expand Up @@ -92,13 +105,13 @@ def scan_options(actions):
yield format_option(arg)


def format_positional_argument(arg):
def format_positional_argument(arg) -> Tuple[List[str], str]:
desc = (arg.help or "") % {"default": arg.default}
name = (arg.metavar or arg.dest).lower()
return [name], desc


def format_option(arg):
def format_option(arg) -> Tuple[List[str], str]:
desc = (arg.help or "") % {"default": arg.default}

if not isinstance(arg, (argparse._StoreAction, argparse._AppendAction)):
Expand All @@ -120,7 +133,7 @@ def format_option(arg):
return names, desc


def import_object(import_name):
def import_object(import_name: str):
module_name, expr = import_name.split(":", 1)
try:
mod = __import__(module_name)
Expand Down Expand Up @@ -149,7 +162,7 @@ def import_object(import_name):
raise

mod = reduce(getattr, module_name.split(".")[1:], mod)
globals_ = builtins
globals_: Dict[str, Any] = builtins # type: ignore[assignment]
if not isinstance(globals_, dict):
globals_ = globals_.__dict__
return eval(expr, globals_, mod.__dict__)
Expand Down Expand Up @@ -249,16 +262,16 @@ def run(self):


def render_rst(
title,
title: str,
options,
is_program,
is_subgroup,
description,
usage,
usage_strip,
usage_codeblock,
epilog,
):
is_program: bool,
is_subgroup: bool,
description: Optional[str],
usage: str,
usage_strip: bool,
usage_codeblock: bool,
epilog: Optional[str],
) -> Iterable[str]:
if usage_strip:
to_strip = title.rsplit(" ", 1)[0]
len_to_strip = len(to_strip) - 4
Expand Down Expand Up @@ -307,7 +320,7 @@ def render_rst(
yield line or ""


def patch_option_role_to_allow_argument_form():
def patch_option_role_to_allow_argument_form() -> None:
"""Before Sphinx 1.2.2, :rst:dir:`.. option::` directive hadn't
allowed to not start with a dash or slash, so it hadn't been possible
to represent positional arguments (not options).
Expand All @@ -320,7 +333,7 @@ def patch_option_role_to_allow_argument_form():
std.option_desc_re = re.compile(r"((?:/|-|--)?[-_a-zA-Z0-9]+)(\s*.*)")


def setup(app):
def setup(app) -> Dict[str, bool]:
app.add_directive("autoprogram", AutoprogramDirective)
patch_option_role_to_allow_argument_form()
return {
Expand All @@ -330,7 +343,7 @@ def setup(app):


class ScannerTestCase(unittest.TestCase):
def test_simple_parser(self):
def test_simple_parser(self) -> None:
parser = argparse.ArgumentParser(description="Process some integers.")
parser.add_argument(
"integers",
Expand Down Expand Up @@ -389,7 +402,7 @@ def test_simple_parser(self):
options[4],
)

def test_subcommands(self):
def test_subcommands(self) -> None:
parser = argparse.ArgumentParser(description="Process some integers.")
subparsers = parser.add_subparsers()
max_parser = subparsers.add_parser("max", description="Find the max.")
Expand Down Expand Up @@ -437,7 +450,7 @@ def test_subcommands(self):
self.assertEqual(2, len(options))
self.assertEqual((["n"], "An integer for the accumulator."), options[0])

def test_argument_groups(self):
def test_argument_groups(self) -> None:
parser = argparse.ArgumentParser(description="This is a program.")
parser.add_argument("-v", action="store_true", help="A global argument")
plain_group = parser.add_argument_group(
Expand Down Expand Up @@ -487,14 +500,14 @@ def test_argument_groups(self):
self.assertEqual(1, len(options))
self.assertEqual((["fancy"], "Set the fancyness"), options[0])

def test_choices(self):
def test_choices(self) -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--awesomeness", choices=["meh", "awesome"])
program, options, cmd_parser = list(scan_programs(parser))[0]
_program, options, _cmd_parser = list(scan_programs(parser))[0]
log_option = options[1]
self.assertEqual((["--awesomeness {meh,awesome}"], ""), log_option)

def test_parse_epilog(self):
def test_parse_epilog(self) -> None:
parser = argparse.ArgumentParser(
description="Process some integers.",
epilog="The integers will be processed.",
Expand All @@ -503,12 +516,12 @@ def test_parse_epilog(self):
programs = list(programs)
self.assertEqual(1, len(programs))
(parser_data,) = programs
program, options, cmd_parser = parser_data
_program, _options, cmd_parser = parser_data
self.assertEqual("The integers will be processed.", cmd_parser.epilog)


class AutoprogramDirectiveTestCase(unittest.TestCase):
def setUp(self):
def setUp(self) -> None:
self.untouched_sys_path = sys.path[:]
sample_prog_path = os.path.join(os.path.dirname(__file__), "..", "doc")
sys.path.insert(0, sample_prog_path)
Expand All @@ -524,16 +537,16 @@ def setUp(self):
None,
)

def tearDown(self):
def tearDown(self) -> None:
sys.path[:] = self.untouched_sys_path

def test_make_rst(self):
def test_make_rst(self) -> None:
"""Alt least it shouldn't raise errors during making RST string."""
list(self.directive.make_rst())


class UtilTestCase(unittest.TestCase):
def test_import_object(self):
def test_import_object(self) -> None:
cls = import_object("sphinxcontrib.autoprogram:UtilTestCase")
self.assertTrue(cls is UtilTestCase)
instance = import_object(
Expand All @@ -543,7 +556,7 @@ def test_import_object(self):

if not hasattr(unittest.TestCase, "assertIsInstance"):

def assertIsInstance(self, instance, cls):
def assertIsInstance(self, instance, cls) -> None: # type: ignore[override]
self.assertTrue(
isinstance(instance, cls),
"{0!r} is not an instance of {1.__module__}."
Expand Down
18 changes: 14 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
[tox]
envlist =
{py37,py38,py39}-{sphinx34,sphinx33,sphinx32,sphinx24,sphinx18}
{py27,pypy}-{sphinx18}
black
flake8
black flake8 mypy pylint
minversion = 2.7.0

[testenv]
Expand Down Expand Up @@ -47,4 +45,16 @@ commands =
deps =
black
commands =
black --check --diff sphinxcontrib
black --check --diff sphinxcontrib

[testenv:mypy]
deps =
mypy
commands =
mypy sphinxcontrib

[testenv:pylint]
deps =
pylint
commands =
pylint sphinxcontrib

0 comments on commit bce41f9

Please sign in to comment.