Skip to content

Commit

Permalink
Fix ipv6 detection (#250)
Browse files Browse the repository at this point in the history
* Fix NEWS
* Change IPv6 detection equality test to membership
* Add Signing Key info to README.rst
* Update PyPI Long Description
* README.rst improvements
* Make housekeep.py less cluttery
* Update NEWS.rst
* Create 'hidden' testenv for static type checking
  • Loading branch information
pepoluan committed Feb 19, 2021
1 parent 844e7d5 commit 93d67ed
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 29 deletions.
51 changes: 51 additions & 0 deletions DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
######################################
aiosmtpd - asyncio based SMTP server
######################################

| |github license| |GA badge| |codecov| |LGTM.com| |readthedocs| |PyPI|
|
.. |github license| image:: https://img.shields.io/github/license/aio-libs/aiosmtpd
:target: https://github.com/aio-libs/aiosmtpd/blob/master/LICENSE
:alt: Project License on GitHub
.. .. For |GA badge|, don't forget to check actual workflow name in unit-testing-and-coverage.yml
.. |GA badge| image:: https://github.com/aio-libs/aiosmtpd/workflows/aiosmtpd%20CI/badge.svg
:target: https://github.com/aio-libs/aiosmtpd/actions
:alt: GitHub Actions status
.. |codecov| image:: https://codecov.io/github/aio-libs/aiosmtpd/coverage.svg?branch=master
:target: https://codecov.io/github/aio-libs/aiosmtpd?branch=master
:alt: Code Coverage
.. |LGTM.com| image:: https://img.shields.io/lgtm/grade/python/github/aio-libs/aiosmtpd.svg?logo=lgtm&logoWidth=18
:target: https://lgtm.com/projects/g/aio-libs/aiosmtpd/context:python
:alt: Semmle/LGTM.com quality
.. |readthedocs| image:: https://readthedocs.org/projects/aiosmtpd/badge/?version=latest
:target: https://aiosmtpd.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. |PyPI| image:: https://badge.fury.io/py/aiosmtpd.svg
:target: https://badge.fury.io/py/aiosmtpd
:alt: PyPI Package
.. .. Do NOT include the Discourse badge!
This is a server for SMTP and related protocols, similar in utility to the
standard library's ``smtpd.py module``, but rewritten to be based on ``asyncio`` for
Python 3.6+.

Please visit the `Project Homepage`_ for more information.

.. _`Project Homepage`: https://aiosmtpd.readthedocs.io/


Signing Keys
============

Starting version 1.3.1,
files provided through PyPI or `GitHub Releases`_
will be signed using one of the following GPG Keys:

+-------------------------+----------------+------------------------------+
| GPG Key ID | Owner | Email |
+=========================+================+==============================+
| ``5D60 CE28 9CD7 C258`` | Pandu E POLUAN | pepoluan at gmail period com |
+-------------------------+----------------+------------------------------+

.. _`GitHub Releases`: https://github.com/aio-libs/aiosmtpd/releases
38 changes: 34 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
.. |github license| image:: https://img.shields.io/github/license/aio-libs/aiosmtpd
:target: https://github.com/aio-libs/aiosmtpd/blob/master/LICENSE
:alt: Project License on GitHub
.. .. Fpr |GA badge|, don't forget to check actual workflow name in unit-testing-and-coverage.yml
.. |GA badge| image:: https://github.com/aio-libs/aiosmtpd/workflows/aiosmtpd%20CI/badge.svg
:target: https://github.com/aio-libs/aiosmtpd/actions
:alt: GitHub Actions status
.. .. Don't forget to check actual workflow name in unit-testing-and-coverage.yml
.. |codecov| image:: https://codecov.io/github/aio-libs/aiosmtpd/coverage.svg?branch=master
:target: https://codecov.io/github/aio-libs/aiosmtpd?branch=master
:alt: Code Coverage
Expand All @@ -25,9 +25,11 @@
.. |PyPI| image:: https://badge.fury.io/py/aiosmtpd.svg
:target: https://badge.fury.io/py/aiosmtpd
:alt: PyPI Package
.. .. If you edit the above badges, don't forget to edit setup.cfg
.. .. The |Discourse| badge MUST NOT be included in setup.cfg
.. |Discourse| image:: https://img.shields.io/discourse/status?server=https%3A%2F%2Faio-libs.discourse.group%2F&style=social
:target: https://aio-libs.discourse.group/
:alt: Discourse status
:alt: Discourse

The Python standard library includes a basic |SMTP|_ server in the |smtpd|_ module,
based on the old asynchronous libraries |asyncore|_ and |asynchat|_.
Expand Down Expand Up @@ -199,8 +201,19 @@ have been configured and tested:

* ``docs``

Builds HTML documentation using Sphinx. A `pytest doctest`_ will run prior to
actual building of the documentation.
Builds **HTML documentation** using Sphinx.
A `pytest doctest`_ will run prior to actual building of the documentation.

* ``static``

Performs a **static type checking** using ``pytype``.
Please ensure that `all its dependencies`_ have been installed before
executing this testenv.

**Note:** Because ``pytype`` does not run on Windows,
This testenv must be invoked explicitly; it will not automatically run.

.. _`all its dependencies`: https://github.com/google/pytype/blob/2021.02.09/CONTRIBUTING.md#pytype-dependencies


Environment Variables
Expand Down Expand Up @@ -268,6 +281,23 @@ and the cached Python bytecode messes up execution
will cause problems as Python becomes confused about the locations of the source code).


Signing Keys
============

Starting version 1.3.1,
files provided through `PyPI`_ or `GitHub Releases`_
will be signed using one of the following GPG Keys:

+-------------------------+----------------+------------------------------+
| GPG Key ID | Owner | Email |
+=========================+================+==============================+
| ``5D60 CE28 9CD7 C258`` | Pandu E POLUAN | pepoluan at gmail period com |
+-------------------------+----------------+------------------------------+

.. _PyPI: https://pypi.org/project/aiosmtpd/
.. _`GitHub Releases`: https://github.com/aio-libs/aiosmtpd/releases


License
=======

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.3.1"
__version__ = "1.3.2a1"
20 changes: 13 additions & 7 deletions aiosmtpd/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@
AsyncServer = asyncio.base_events.Server


@public
class IP6_IS:
# Apparently errno.E* constants adapts to the OS, so on Windows they will
# automatically use the WSAE* constants
NO = {errno.EADDRNOTAVAIL, errno.EAFNOSUPPORT}
YES = {errno.EADDRINUSE}


def _has_ipv6():
# Helper function to assist in mocking
return has_ipv6


@public
def get_localhost() -> str:
# Ref:
# - https://github.com/urllib3/urllib3/pull/611#issuecomment-100954017
Expand All @@ -44,14 +53,11 @@ def get_localhost() -> str:
# unused port), so IPv6 is definitely supported
return "::1"
except OSError as e:
# Apparently errno.E* constants adapts to the OS, so on Windows they will
# automatically use the WSAE* constants
if e.errno == errno.EADDRNOTAVAIL:
# Getting (WSA)EADDRNOTAVAIL means IPv6 is not supported
if e.errno in IP6_IS.NO:
return "127.0.0.1"
if e.errno == errno.EADDRINUSE:
# Getting (WSA)EADDRINUSE means IPv6 *is* supported, but already used.
# Shouldn't be possible, but just in case...
if e.errno in IP6_IS.YES:
# We shouldn't ever get these errors, but if we do, that means IPv6 is
# supported
return "::1"
# Other kinds of errors MUST be raised so we can inspect
raise
Expand Down
15 changes: 12 additions & 3 deletions aiosmtpd/docs/NEWS.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
===================
###################
NEWS for aiosmtpd
===================
###################


1.3.2 (2021-02-19)
==================

Fixed/Improved
--------------
* Fixed Documentation Issues that might cause automatic package builders to fail
* Also consider ``EAFNOSUPPORT`` in IPv6 detection (Closes #244, again)


1.3.1 (2021-02-18)
==================

Fixed/Improved
==============
--------------
* ``ready_timeout`` now actually enforced, raising ``TimeoutError`` if breached
* Hides only expected exceptions raised by ``Controller._testconn()``
* No longer fail with opaque "Unknown Error" if ``hostname=""`` (Closes #244)
Expand Down
26 changes: 26 additions & 0 deletions aiosmtpd/docs/controller.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,32 @@ Controller API

.. py:module:: aiosmtpd.controller
.. class:: IP6_IS

.. py:attribute:: NO
:type: set

Contains constants from :mod:`errno` that will be raised by `socket.bind()`
if IPv6 is not available on the system.

.. important::

If your system does not have IPv6 support but :func:`get_localhost`
raises an error instead of returning ``"127.0.0.1"``,
you can add the error number into this attribute.

.. py:attribute:: YES
:type: set

Contains constants from :mod:`errno` that will be raised by `socket.bind()`
if IPv6 is not available on the system.

.. py:function:: get_localhost
:return: The numeric address of the loopback interface; ``"::1"`` if IPv6 is supported,
``"127.0.0.1"`` if IPv6 is not supported.
:rtype: str

.. class:: Controller(\
handler, loop=None, hostname=None, port=8025, \
*, \
Expand Down
12 changes: 9 additions & 3 deletions aiosmtpd/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,13 @@ def test_getlocalhost_6yes(self, mocker: MockFixture):
mock_makesock.assert_called_with(socket.AF_INET6, socket.SOCK_STREAM)
assert mock_sock.bind.called

def test_getlocalhost_6no(self, mocker):
# Apparently errno.E* constants adapts to the OS, so on Windows they will
# automatically use the analogous WSAE* constants
@pytest.mark.parametrize(
"err",
[errno.EADDRNOTAVAIL, errno.EAFNOSUPPORT]
)
def test_getlocalhost_6no(self, mocker, err):
mock_makesock: mocker.Mock = mocker.patch(
"aiosmtpd.controller.makesock",
side_effect=OSError(errno.EADDRNOTAVAIL, "Mock IP4-only"),
Expand All @@ -250,11 +256,11 @@ def test_getlocalhost_6inuse(self, mocker):
def test_getlocalhost_error(self, mocker):
mock_makesock: mocker.Mock = mocker.patch(
"aiosmtpd.controller.makesock",
side_effect=OSError(errno.EAFNOSUPPORT, "Mock Error"),
side_effect=OSError(errno.EFAULT, "Mock Error"),
)
with pytest.raises(OSError, match="Mock Error") as exc:
get_localhost()
assert exc.value.errno == errno.EAFNOSUPPORT
assert exc.value.errno == errno.EFAULT
mock_makesock.assert_called_with(socket.AF_INET6, socket.SOCK_STREAM)


Expand Down
8 changes: 4 additions & 4 deletions housekeep.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Style:
# region #### Helper funcs ############################################################


def deldir(targ: Path):
def deldir(targ: Path, verbose: bool = True):
if not targ.exists():
return
for i, pp in enumerate(reversed(sorted(targ.rglob("*"))), start=1):
Expand All @@ -72,7 +72,7 @@ def deldir(targ: Path):
pp.rmdir()
else:
raise RuntimeError(f"Don't know how to handle '{pp}'")
if (i & 1023) == 0:
if verbose and (i & 0x1FF) == 0:
print(".", end="", flush=True)
targ.rmdir()

Expand Down Expand Up @@ -113,13 +113,13 @@ def pycache_clean(verbose=False):
"""Cleanup __pycache__ dirs & bytecode files (if any)"""
aiosmtpdpath = Path(".")
for i, f in enumerate(aiosmtpdpath.rglob("*.py[co]"), start=1):
if verbose and (i % 63) == 0:
if verbose and (i % 0x3FF) == 0:
print(".", end="", flush=True)
f.unlink()
for d in aiosmtpdpath.rglob("__pycache__"):
if verbose:
print(".", end="", flush=True)
d.rmdir()
deldir(d, verbose)
if verbose:
print()

Expand Down
2 changes: 1 addition & 1 deletion release.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
if has_verify:
print("Waiting for package to be received by PyPI...", end="")
for i in range(10, 0, -1):
print(i, end=" ")
print(i, end="..")
time.sleep(1.0)
print()
twine_verif = ["twine", "verify_upload"] + DISTFILES
Expand Down
7 changes: 1 addition & 6 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
name = aiosmtpd
version = attr: aiosmtpd.__version__
description = aiosmtpd - asyncio based SMTP server
long_description =
This is a server for SMTP and related protocols, similar in utility to the
standard library's smtpd.py module, but rewritten to be based on asyncio for
Python 3.
Please visit the Project Homepage for more information.
long_description = file: DESCRIPTION.rst
long_description_content_type = text/x-rst
url = https://aiosmtpd.readthedocs.io/
project_urls =
Expand Down
15 changes: 15 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ deps:
sphinx_rtd_theme
pickle5 ; python_version < '3.8'

[testenv:static]
basepython = python3
envdir = {toxworkdir}/static
commands =
python housekeep.py prep
pytype --keep-going .
deps:
pytype
# Deps of conf.py
sphinx_rtd_theme
# Deps of test files
pytest
pytest-mock
packaging

# I'd love to fold flake8 into pyproject.toml, because the flake8 settings
# should be "project-wide" settings (enforced not only during tox).
# But the flake8 maintainers seem to harbor a severe dislike of pyproject.toml.
Expand Down

0 comments on commit 93d67ed

Please sign in to comment.