Skip to content

Commit

Permalink
Code Hygiene (#259)
Browse files Browse the repository at this point in the history
* Activate LOTS of flake8 plugins to enforce code hygiene
* Tune Annotation Thresholds
* Add Annotation
* Add pytest-mock to the deps of "docs"
* Fix post-rebase flake8 complaints
* Update NEWS.rst
* Move flake8 plugins into a pseudo-section in tox.ini
* Create concrete class for MessageHandler
* Bump Version to 1.5.0a2
* Experimentally enable tox-ing on 3.10
* Use typing.ByteString instead of custom AnyBytes
  • Loading branch information
pepoluan committed Mar 23, 2021
1 parent 747a7c4 commit 1a1c1bb
Show file tree
Hide file tree
Showing 26 changed files with 627 additions and 331 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/unit-testing-and-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,17 @@ jobs:
python -m pip install --upgrade pip setuptools wheel
python setup.py develop
- name: "flake8 Style Checking"
shell: bash
# language=bash
run: |
pip install colorama flake8 flake8-bugbear
# A bunch of flake8 plugins...
grab_f8_plugins=(
"from configparser import ConfigParser;"
"config = ConfigParser();"
"config.read('tox.ini');"
"print(config['flake8_plugins']['deps']);"
)
pip install colorama flake8 $(python -c "${grab_f8_plugins[*]}")
python -m flake8 aiosmtpd setup.py housekeep.py release.py
- name: "Docs Checking"
# language=bash
Expand Down
2 changes: 1 addition & 1 deletion aiosmtpd/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2014-2021 The aiosmtpd Developers
# SPDX-License-Identifier: Apache-2.0

__version__ = "1.5.0a1"
__version__ = "1.5.0a2"
10 changes: 6 additions & 4 deletions aiosmtpd/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,17 @@ class _FakeServer(asyncio.StreamReaderProtocol):
factory() failed to instantiate an SMTP instance.
"""

def __init__(self, loop):
def __init__(self, loop: asyncio.AbstractEventLoop):
# Imitate what SMTP does
super().__init__(
asyncio.StreamReader(loop=loop),
client_connected_cb=self._client_connected_cb,
loop=loop,
)

def _client_connected_cb(self, reader, writer):
def _client_connected_cb(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
pass


Expand Down Expand Up @@ -143,7 +145,7 @@ def factory(self):
"""Subclasses can override this to customize the handler/server creation."""
return SMTP(self.handler, **self.SMTP_kwargs)

def _factory_invoker(self):
def _factory_invoker(self) -> Union[SMTP, _FakeServer]:
"""Wraps factory() to catch exceptions during instantiation"""
try:
self.smtpd = self.factory()
Expand Down Expand Up @@ -223,7 +225,7 @@ class (it's lazy initialization, done only on initial connection).
"""
raise NotImplementedError

def _run(self, ready_event: threading.Event):
def _run(self, ready_event: threading.Event) -> None:
asyncio.set_event_loop(self.loop)
try:
# Need to do two-step assignments here to ensure IDEs can properly
Expand Down
5 changes: 3 additions & 2 deletions aiosmtpd/docs/NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
###################


1.5.0 (aiosmtpd-next-next)
==========================
1.5.0 (aiosmtpd-next)
=====================

Added
-----
Expand All @@ -13,6 +13,7 @@ Added
Fixed/Improved
--------------
* All Controllers now have more rationale design, as they are now composited from a Base + a Mixin
* A whole bunch of annotations


1.4.2 (2021-03-08)
Expand Down
64 changes: 37 additions & 27 deletions aiosmtpd/docs/_exts/autoprogramm.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@
import builtins
import collections
import os
import sphinx

from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.parsers.rst.directives import unchanged
from docutils.statemachine import StringList
from functools import reduce
from sphinx.util.nodes import nested_parse_with_titles
from typing import List
from typing import Any, Dict, List, Optional, Tuple


__all__ = ("AutoprogrammDirective", "import_object", "scan_programs", "setup")


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

if isinstance(neg1_action, argparse._SubParsersAction):
Expand All @@ -56,7 +57,13 @@ def get_subparser_action(parser):
return a


def scan_programs(parser, command=None, maxdepth=0, depth=0, groups=False):
def scan_programs(
parser: argparse.ArgumentParser,
command: List[str] = None,
maxdepth: int = 0,
depth: int = 0,
groups: bool = False,
):
if command is None:
command = []

Expand All @@ -79,6 +86,7 @@ def scan_programs(parser, command=None, maxdepth=0, depth=0, groups=False):
subp_action = get_subparser_action(parser)

if subp_action:
# noinspection PyUnresolvedReferences
choices = subp_action.choices.items()

if not (
Expand All @@ -89,11 +97,10 @@ def scan_programs(parser, command=None, maxdepth=0, depth=0, groups=False):

for cmd, sub in choices:
if isinstance(sub, argparse.ArgumentParser):
for program in scan_programs(sub, command + [cmd], maxdepth, depth + 1):
yield program
yield from scan_programs(sub, command + [cmd], maxdepth, depth + 1)


def scan_options(actions):
def scan_options(actions: list):
for arg in actions:
if not (arg.option_strings or isinstance(arg, argparse._SubParsersAction)):
yield format_positional_argument(arg)
Expand All @@ -103,13 +110,13 @@ def scan_options(actions):
yield format_option(arg)


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


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

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


def import_object(import_name):
def import_object(import_name: str) -> Any:
module_name, expr = import_name.split(":", 1)
try:
mod = __import__(module_name)
Expand All @@ -151,7 +158,8 @@ def import_object(import_name):
with open(f[0]) as fobj:
codestring = fobj.read()
foo = imp.new_module("foo")
exec(codestring, foo.__dict__) # nosec
# noinspection BuiltinExec
exec(codestring, foo.__dict__) # noqa: DUO105 # nosec

sys.modules["foo"] = foo
mod = __import__("foo")
Expand All @@ -163,7 +171,7 @@ def import_object(import_name):
globals_ = builtins
if not isinstance(globals_, dict):
globals_ = globals_.__dict__
return eval(expr, globals_, mod.__dict__) # nosec
return eval(expr, globals_, mod.__dict__) # noqa: DUO104 # nosec


class AutoprogrammDirective(Directive):
Expand Down Expand Up @@ -204,13 +212,16 @@ def make_rst(self):

if start_command:

def get_start_cmd_parser(p):
def get_start_cmd_parser(
p: argparse.ArgumentParser,
) -> argparse.ArgumentParser:
looking_for = start_command.pop(0)
action = get_subparser_action(p)

if not action:
raise ValueError("No actions for command " + looking_for)

# noinspection PyUnresolvedReferences
subp = action.choices[looking_for]

if start_command:
Expand Down Expand Up @@ -263,7 +274,7 @@ def get_start_cmd_parser(p):
options_adornment=options_adornment,
)

def run(self):
def run(self) -> list:
node = nodes.section()
node.document = self.state.document
result = StringList()
Expand All @@ -274,17 +285,17 @@ def run(self):


def render_rst(
title,
options,
is_program,
is_subgroup,
description,
usage,
usage_strip,
usage_codeblock,
epilog,
options_title,
options_adornment,
title: str,
options: List[Tuple[List[str], str]],
is_program: bool,
is_subgroup: bool,
description: str,
usage: Optional[str],
usage_strip: bool,
usage_codeblock: bool,
epilog: str,
options_title: str,
options_adornment: str,
):
if usage_strip:
to_strip = title.rsplit(" ", 1)[0]
Expand All @@ -310,8 +321,7 @@ def render_rst(
yield ("!" if is_subgroup else "?") * len(title)
yield ""

for line in (description or "").splitlines():
yield line
yield from (description or "").splitlines()
yield ""

if usage is None:
Expand Down Expand Up @@ -340,7 +350,7 @@ def render_rst(
yield line or ""


def setup(app):
def setup(app: sphinx.application.Sphinx) -> Dict[str, Any]:
app.add_directive("autoprogramm", AutoprogrammDirective)
return {
"version": "0.2a0",
Expand Down
9 changes: 5 additions & 4 deletions aiosmtpd/docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
#
# aiosmtpd documentation build configuration file, created by
# Copyright 2014-2021 The aiosmtpd Developers
# SPDX-License-Identifier: Apache-2.0

# aiosmtpd documentation build configuration file, originally created by
# sphinx-quickstart on Fri Oct 16 12:18:52 2015.
#
# This file is execfile()d with the current directory set to its
Expand Down Expand Up @@ -331,5 +332,5 @@ def syspath_insert(pth: Path):
# endregion


def setup(app):
def setup(app): # noqa: ANN001
app.add_css_file("aiosmtpd.css")
6 changes: 3 additions & 3 deletions aiosmtpd/docs/proxyprotocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ Enums
Valid only for address family of :attr:`AF.INET` or :attr:`AF.INET6`

.. py:attribute:: rest
:type: Union[bytes, bytearray]
:type: ByteString

The contents depend on the version of the PROXY header *and* (for version 2)
the address family.
Expand Down Expand Up @@ -374,7 +374,7 @@ Enums
.. py:classmethod:: from_raw(raw) -> Optional[ProxyTLV]
:param raw: The raw bytes containing the TLV Vectors
:type raw: Union[bytes, bytearray]
:type raw: ByteString
:return: A new instance of ProxyTLV, or ``None`` if parsing failed

This triggers the parsing of raw bytes/bytearray into a ProxyTLV instance.
Expand All @@ -387,7 +387,7 @@ Enums
.. py:classmethod:: parse(chunk, partial_ok=True) -> Dict[str, Any]
:param chunk: The bytes to parse into TLV Vectors
:type chunk: Union[bytes, bytearray]
:type chunk: ByteString
:param partial_ok: If ``True``, return partially-parsed TLV Vectors as is.
If ``False``, (re)raise ``MalformedTLV``
:type partial_ok: bool
Expand Down
2 changes: 1 addition & 1 deletion aiosmtpd/docs/smtp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ aiosmtpd.smtp

:param challenge: The SMTP AUTH challenge to send to the client.
May be in plaintext, may be in base64. Do NOT prefix with "334 "!
:type challenge: Union[str, bytes, bytearray]
:type challenge: AnyStr
:param encode_to_b64: If true, will perform base64-encoding before sending
the challenge to the client.
:type encode_to_b64: bool
Expand Down

0 comments on commit 1a1c1bb

Please sign in to comment.