New Upstream Release - python-aiosmtpd
Ready changes
Summary
Merged new upstream version: 1.4.4.post2 (was: 1.4.3).
Diff
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..4ed9ab7
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,41 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+ schedule:
+ - cron: "42 17 * * 1"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ python ]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ queries: +security-and-quality
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/unit-testing-and-coverage.yml b/.github/workflows/unit-testing-and-coverage.yml
index e21813b..e867f07 100644
--- a/.github/workflows/unit-testing-and-coverage.yml
+++ b/.github/workflows/unit-testing-and-coverage.yml
@@ -8,17 +8,19 @@ on:
paths:
- "aiosmtpd/**"
- "setup.cfg" # To monitor changes in dependencies
+ - "pyproject.toml" # To monitor changes in dependencies
# This is for PRs
pull_request:
branches: [ "master" ]
paths:
- "aiosmtpd/**"
- "setup.cfg" # To monitor changes in dependencies
+ - "pyproject.toml" # To monitor changes in dependencies
# Manual/on-demand
workflow_dispatch:
# When doing "releases"
release:
- types: [ "created", "edited", "published", "prereleased", "released" ]
+ types: [ "created", "edited" ]
jobs:
qa_docs:
@@ -27,7 +29,7 @@ jobs:
- name: "Checkout latest PR commit"
uses: actions/checkout@v3
- name: "Set up Python"
- uses: actions/setup-python@v4.3.1
+ uses: actions/setup-python@v4
with:
# 3.8 is chosen because it seems to be the fastest for <3.9
# (3.9 excluded because it seems to be still very unstable)
@@ -50,7 +52,7 @@ jobs:
"config.read('tox.ini');"
"print(config['flake8_plugins']['deps']);"
)
- pip install flake8 $(python -c "${grab_f8_plugins[*]}")
+ pip install "flake8>=5.0.4" $(python -c "${grab_f8_plugins[*]}")
python -m flake8 aiosmtpd setup.py housekeep.py release.py
- name: "Docs Checking"
# language=bash
@@ -60,7 +62,7 @@ jobs:
sphinx-build --color -b doctest -d build/.doctree aiosmtpd/docs build/doctest
sphinx-build --color -b html -d build/.doctree aiosmtpd/docs build/html
sphinx-build --color -b man -d build/.doctree aiosmtpd/docs build/man
- - name: "Static Code Checking"
+ - name: "Static Type Checking"
# language=bash
run: |
# Required by examples
@@ -88,15 +90,8 @@ jobs:
# If a matrix fail, do NOT stop other matrix, let them run to completion
fail-fast: false
matrix:
- os: [ "macos-11", "macos-12", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", "windows-latest" ]
- python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.6", "pypy3.7", "pypy3.8" ]
- exclude:
- - os: windows-latest
- python-version: pypy3.6
- - os: macos-11
- python-version: pypy3.6
- - os: macos-12
- python-version: pypy3.6
+ python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8", "pypy3.9" ]
+ os: [ "macos-11", "macos-12", "ubuntu-18.04", "ubuntu-20.04", "ubuntu-22.04", "windows-2019", "windows-2022" ]
runs-on: ${{ matrix.os }}
timeout-minutes: 15 # Slowest so far is pypy3 on MacOS, taking almost 7m
steps:
@@ -105,7 +100,7 @@ jobs:
with:
fetch-depth: 0 # Required by codecov/codecov-action@v1
- name: "Set up Python ${{ matrix.python-version }}"
- uses: actions/setup-python@v4.3.1
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: "Install dependencies"
@@ -113,7 +108,7 @@ jobs:
run: |
python -m pip install --upgrade pip setuptools wheel
# Test deps
- pip install colorama coverage[toml] coverage-conditional-plugin packaging pytest pytest-cov pytest-mock
+ pip install colorama "coverage>=7.0.1" coverage[toml] "coverage-conditional-plugin>=0.5.0" packaging pytest pytest-cov pytest-mock
# Package deps
python setup.py develop
- name: "Security checking"
@@ -121,6 +116,12 @@ jobs:
run: |
pip install bandit
bandit -c bandit.yml -r aiosmtpd
+
+ # IMPORTANT: pypy3.8 is currently excluded from coverage testing because coverage seems to be unstable when
+ # running on PyPy 3.8. This is still under investigation (See issue #325)
+ # Edit: This is due to change in behavior in PyPy v7.3.10, and coverage.py needs to adapt.
+ # See: https://github.com/nedbat/coveragepy/issues/1515
+
- name: "Execute testing with coverage"
if: matrix.python-version != 'pypy3.8'
shell: bash
@@ -143,10 +144,11 @@ jobs:
fi
pytest
#
+
- name: "Report to codecov"
# Ubuntu 18.04 came out of the box with 3.6, and LOTS of system are still running
# 18.04 happily, so we choose this as the 'canonical' code coverage testing.
# One day we'll have to revisit this and bump the version ...
# 2022-12-16: Bumped to Python 3.8 and Ubuntu 20.04. Yeah, we take the conservative LTS route.
if: matrix.python-version == '3.8' && matrix.os == 'ubuntu-20.04'
- uses: codecov/codecov-action@v3.1.1
+ uses: codecov/codecov-action@v3
diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst
index f341b0a..036caf4 100644
--- a/DESCRIPTION.rst
+++ b/DESCRIPTION.rst
@@ -2,11 +2,15 @@
aiosmtpd - asyncio based SMTP server
######################################
-| |github license| |_| |PyPI Version| |_| |PyPI Python|
-| |GA badge| |_| |codecov| |_| |LGTM.com| |_| |readthedocs|
+| |github license| |_| |PyPI Version| |_| |PyPI Python| |_| |PyPI PythonImpl|
+| |GA badge| |_| |CodeQL badge| |_| |codecov| |_| |readthedocs|
| |GH Release| |_| |GH PRs| |_| |GH LastCommit|
+| |PyPI DL| |_| |GH DL|
+|
+| |GH Discussions|
|
+.. .. U+00A0 is non-breaking space
.. |_| unicode:: 0xA0
:trim:
.. |github license| image:: https://img.shields.io/github/license/aio-libs/aiosmtpd?logo=Open+Source+Initiative&logoColor=0F0
@@ -18,21 +22,22 @@
.. |PyPI Python| image:: https://img.shields.io/pypi/pyversions/aiosmtpd?logo=python&logoColor=yellow
:target: https://pypi.org/project/aiosmtpd/
:alt: Supported Python Versions
+.. |PyPI PythonImpl| image:: https://img.shields.io/pypi/implementation/aiosmtpd?logo=python
+ :target: https://pypi.org/project/aiosmtpd/
+ :alt: Supported Python Implementations
.. .. 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
+ :target: https://github.com/aio-libs/aiosmtpd/actions/workflows/unit-testing-and-coverage.yml
+ :alt: GitHub CI status
+.. |CodeQL badge| image:: https://github.com/aio-libs/aiosmtpd/workflows/CodeQL/badge.svg
+ :target: https://github.com/aio-libs/aiosmtpd/actions/workflows/codeql.yml
+ :alt: CodeQL 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://img.shields.io/readthedocs/aiosmtpd?logo=Read+the+Docs
:target: https://aiosmtpd.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
-.. .. Do NOT include the Discourse badge!
-.. .. Below are badges just for PyPI
.. |GH Release| image:: https://img.shields.io/github/v/release/aio-libs/aiosmtpd?logo=github
:target: https://github.com/aio-libs/aiosmtpd/releases
:alt: GitHub latest release
@@ -42,6 +47,15 @@
.. |GH LastCommit| image:: https://img.shields.io/github/last-commit/aio-libs/aiosmtpd?logo=GitHub
:target: https://github.com/aio-libs/aiosmtpd/commits/master
:alt: GitHub last commit
+.. |PyPI DL| image:: https://img.shields.io/pypi/dm/aiosmtpd?logo=pypi
+ :target: https://pypi.org/project/aiosmtpd/
+ :alt: PyPI monthly downloads
+.. |GH DL| image:: https://img.shields.io/github/downloads/aio-libs/aiosmtpd/total?logo=github
+ :target: https://github.com/aio-libs/aiosmtpd/releases
+ :alt: GitHub downloads
+.. |GH Discussions| image:: https://img.shields.io/github/discussions/aio-libs/aiosmtpd?logo=github&style=social
+ :target: https://github.com/aio-libs/aiosmtpd/discussions
+ :alt: GitHub Discussions
This is a server for SMTP and related MTA protocols,
similar in utility to the standard library's |smtpd.py|_ module,
diff --git a/README.rst b/README.rst
index 9cca496..2714318 100644
--- a/README.rst
+++ b/README.rst
@@ -2,10 +2,11 @@
aiosmtpd - An asyncio based SMTP server
=========================================
-| |github license| |_| |PyPI Version| |_| |PyPI Python|
-| |GA badge| |_| |codecov| |_| |LGTM.com| |_| |readthedocs|
+| |github license| |_| |PyPI Version| |_| |PyPI Python| |_| |PyPI PythonImpl|
+| |GA badge| |_| |CodeQL badge| |_| |codecov| |_| |readthedocs|
+|
+| |GH Discussions|
|
-| |Discourse|
.. |_| unicode:: 0xA0
:trim:
@@ -18,29 +19,31 @@
.. |PyPI Python| image:: https://img.shields.io/pypi/pyversions/aiosmtpd?logo=python&logoColor=yellow
:target: https://pypi.org/project/aiosmtpd/
:alt: Supported Python Versions
+.. |PyPI PythonImpl| image:: https://img.shields.io/pypi/implementation/aiosmtpd?logo=python
+ :target: https://pypi.org/project/aiosmtpd/
+ :alt: Supported Python Implementations
.. .. 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
+ :target: https://github.com/aio-libs/aiosmtpd/actions/workflows/unit-testing-and-coverage.yml
+ :alt: GitHub CI status
+.. |CodeQL badge| image:: https://github.com/aio-libs/aiosmtpd/workflows/CodeQL/badge.svg
+ :target: https://github.com/aio-libs/aiosmtpd/actions/workflows/codeql.yml
+ :alt: CodeQL 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://img.shields.io/readthedocs/aiosmtpd?logo=Read+the+Docs&logoColor=white
:target: https://aiosmtpd.readthedocs.io/en/latest/
:alt: Documentation Status
-.. .. 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
+.. |GH Discussions| image:: https://img.shields.io/github/discussions/aio-libs/aiosmtpd?logo=github&style=social
+ :target: https://github.com/aio-libs/aiosmtpd/discussions
+ :alt: GitHub Discussions
The Python standard library includes a basic |SMTP|_ server in the |smtpd|_ module,
based on the old asynchronous libraries |asyncore|_ and |asynchat|_.
These modules are quite old and are definitely showing their age;
``asyncore`` and ``asynchat`` are difficult APIs to work with, understand, extend, and fix.
+(And have been deprecated since Python 3.6, and will be removed in Python 3.12.)
With the introduction of the |asyncio|_ module in Python 3.4,
a much better way of doing asynchronous I/O is now available.
@@ -64,16 +67,14 @@ Supported Platforms
``aiosmtpd`` has been tested on **CPython**>=3.7 and |PyPy|_>=3.7
for the following platforms (in alphabetical order):
-* Cygwin (on Windows 10) [1]
-* FreeBSD 12 [2]
-* OpenSUSE Leap 15 [2]
+* Cygwin (as of 2022-12-22, only for CPython 3.7, 3.8, and 3.9)
+* MacOS 11 and 12
* Ubuntu 18.04
* Ubuntu 20.04
* Ubuntu 22.04
* Windows 10
-
- | [1] Supported only with Cygwin-provided CPython versions
- | [2] Supported only on the latest minor release
+* Windows Server 2019
+* Windows Server 2022
``aiosmtpd`` *probably* can run on platforms not listed above,
but we cannot provide support for unlisted platforms.
@@ -154,20 +155,16 @@ option::
You can also add the ``-s``/``--capture=no`` option to show output, e.g.::
- $ tox -e py36-nocov -- -s
+ $ tox -e py37-nocov -- -s
and these options can be combined::
- $ tox -e py36-nocov -- -x -s <testname>
+ $ tox -e py37-nocov -- -x -s <testname>
(The ``-e`` parameter is explained in the next section about 'testenvs'.
In general, you'll want to choose the ``nocov`` testenvs if you want to show output,
so you can see which test is generating which output.)
-The `-x` and `-s` options can be combined::
-
- $ tox -e py36-nocov -- -x -s <testname>
-
Supported 'testenvs'
------------------------
@@ -176,7 +173,7 @@ In general, the ``-e`` parameter to tox specifies one (or more) **testenv**
to run (separate using comma if more than one testenv). The following testenvs
have been configured and tested:
-* ``{py37,py38,py39,py310,py311,pypy3}-{nocov,cov,diffcov,profile}``
+* ``{py37,py38,py39,py310,py311,pypy3,pypy37,pypy38,pypy39}-{nocov,cov,diffcov,profile}``
Specifies the interpreter to run and the kind of testing to perform.
@@ -189,7 +186,7 @@ have been configured and tested:
This must be **invoked manually** using the ``-e`` parameter
**Note 1:** As of 2021-02-23,
- only the ``{py36,py38}-{nocov,cov}`` combinations work on **Cygwin**.
+ only the ``{py37,py38,py39}-{nocov,cov}`` combinations work on **Cygwin**.
**Note 2:** It is also possible to use whatever Python version is used when
invoking ``tox`` by using the ``py`` target, but you must explicitly include
diff --git a/aiosmtpd/__init__.py b/aiosmtpd/__init__.py
index e8a5131..4def5e0 100644
--- a/aiosmtpd/__init__.py
+++ b/aiosmtpd/__init__.py
@@ -1,4 +1,21 @@
# Copyright 2014-2021 The aiosmtpd Developers
# SPDX-License-Identifier: Apache-2.0
+import asyncio
+import warnings
-__version__ = "1.4.3"
+
+__version__ = "1.4.4.post2"
+
+
+def _get_or_new_eventloop() -> asyncio.AbstractEventLoop:
+ loop = None
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ try:
+ loop = asyncio.get_event_loop()
+ except (DeprecationWarning, RuntimeError): # pragma: py-lt-310
+ if loop is None: # pragma: py-lt-312
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ assert isinstance(loop, asyncio.AbstractEventLoop)
+ return loop
diff --git a/aiosmtpd/controller.py b/aiosmtpd/controller.py
index 8a336d9..5e07eb4 100644
--- a/aiosmtpd/controller.py
+++ b/aiosmtpd/controller.py
@@ -107,11 +107,11 @@ class _FakeServer(asyncio.StreamReaderProtocol):
# Imitate what SMTP does
super().__init__(
asyncio.StreamReader(loop=loop),
- client_connected_cb=self._client_connected_cb,
+ client_connected_cb=self._cb_client_connected,
loop=loop,
)
- def _client_connected_cb(
+ def _cb_client_connected(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
pass
diff --git a/aiosmtpd/docs/NEWS.rst b/aiosmtpd/docs/NEWS.rst
index 8012822..cf7251e 100644
--- a/aiosmtpd/docs/NEWS.rst
+++ b/aiosmtpd/docs/NEWS.rst
@@ -16,6 +16,23 @@ Fixed/Improved
* A whole bunch of annotations
+1.4.4.post2 (2023-01-19)
+========================
+
+Fixed/Improved
+--------------
+* Prevent unclean repo from being built (Closes #365)
+* Reduce chance of not-ready-for-release packages from being uploaded
+
+
+1.4.4 (2023-01-17)
+==================
+
+Fixed/Improved
+--------------
+* No longer expect an implicit creation of the event loop through ``get_event_loop()`` (Closes #353)
+
+
1.4.3 (2022-12-21)
=====================
diff --git a/aiosmtpd/docs/_exts/autoprogramm.py b/aiosmtpd/docs/_exts/autoprogramm.py
index c23bd2f..de6afc3 100644
--- a/aiosmtpd/docs/_exts/autoprogramm.py
+++ b/aiosmtpd/docs/_exts/autoprogramm.py
@@ -34,9 +34,9 @@ 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 import nodes # pytype: disable=pyi-error
+from docutils.parsers.rst import Directive # pytype: disable=pyi-error
+from docutils.parsers.rst.directives import unchanged # pytype: disable=pyi-error
from docutils.statemachine import StringList
from functools import reduce
from sphinx.util.nodes import nested_parse_with_titles
@@ -46,7 +46,16 @@ from typing import Any, Dict, List, Optional, Tuple
__all__ = ("AutoprogrammDirective", "import_object", "scan_programs", "setup")
-def get_subparser_action(parser: argparse.ArgumentParser) -> argparse._SubParsersAction:
+# Need to temporarily disable this particular check, because although this function
+# is guaranteed to return a proper value (due to how ArgumentParser works), pytype
+# doesn't really know that, and therefore raised an error in the (to its view)
+# possible fallthrough of "implicit return None" if the "for a" loop exits without
+# finding the right item.
+#
+# pytype: disable=bad-return-type
+def get_subparser_action(
+ parser: argparse.ArgumentParser
+) -> argparse._SubParsersAction:
neg1_action = parser._actions[-1]
if isinstance(neg1_action, argparse._SubParsersAction):
@@ -55,11 +64,12 @@ def get_subparser_action(parser: argparse.ArgumentParser) -> argparse._SubParser
for a in parser._actions:
if isinstance(a, argparse._SubParsersAction):
return a
+# pytype: enable=bad-return-type
def scan_programs(
parser: argparse.ArgumentParser,
- command: List[str] = None,
+ command: Optional[List[str]] = None,
maxdepth: int = 0,
depth: int = 0,
groups: bool = False,
@@ -111,8 +121,12 @@ def scan_options(actions: list):
def format_positional_argument(arg: argparse.Action) -> Tuple[List[str], str]:
- desc = (arg.help or "") % {"default": arg.default}
- name = arg.metavar or arg.dest
+ desc: str = (arg.help or "") % {"default": arg.default}
+ name: str
+ if isinstance(arg.metavar, tuple):
+ name = arg.metavar[0]
+ else:
+ name = arg.metavar or arg.dest or ""
return [name], desc
@@ -149,7 +163,6 @@ def import_object(import_name: str) -> Any:
# an ImportError as it did before.
import glob
import sys
- import os
import imp
for p in sys.path:
@@ -298,6 +311,7 @@ def render_rst(
options_adornment: str,
):
if usage_strip:
+ assert usage is not None
to_strip = title.rsplit(" ", 1)[0]
len_to_strip = len(to_strip) - 4
diff --git a/aiosmtpd/docs/controller.rst b/aiosmtpd/docs/controller.rst
index c1edc13..220d45a 100644
--- a/aiosmtpd/docs/controller.rst
+++ b/aiosmtpd/docs/controller.rst
@@ -236,7 +236,8 @@ you'll have to do something similar to this:
.. doctest:: unthreaded
>>> import asyncio
- >>> loop = asyncio.get_event_loop()
+ >>> loop = asyncio.new_event_loop()
+ >>> asyncio.set_event_loop(loop)
>>> from aiosmtpd.controller import UnthreadedController
>>> from aiosmtpd.handlers import Sink
>>> controller = UnthreadedController(Sink(), loop=loop)
@@ -261,7 +262,7 @@ we'll also schedule an autostop so it won't hang:
... loop.run_forever()
>>> import threading
>>> thread = threading.Thread(target=runner)
- >>> thread.setDaemon(True)
+ >>> thread.daemon = True
>>> thread.start()
>>> import time
>>> time.sleep(0.1) # Allow the loop to begin
diff --git a/aiosmtpd/handlers.py b/aiosmtpd/handlers.py
index 5e44f7c..2d1b28f 100644
--- a/aiosmtpd/handlers.py
+++ b/aiosmtpd/handlers.py
@@ -25,6 +25,7 @@ from typing import Any, AnyStr, List, Type, TypeVar, Optional
from public import public
+from aiosmtpd import _get_or_new_eventloop
from aiosmtpd.smtp import SMTP as SMTPServer
from aiosmtpd.smtp import Envelope as SMTPEnvelope
from aiosmtpd.smtp import Session as SMTPSession
@@ -218,7 +219,7 @@ class AsyncMessage(Message, metaclass=ABCMeta):
loop: Optional[asyncio.AbstractEventLoop] = None,
):
super().__init__(message_class)
- self.loop = loop or asyncio.get_event_loop()
+ self.loop = loop or _get_or_new_eventloop()
async def handle_DATA(
self, server: SMTPServer, session: SMTPSession, envelope: SMTPEnvelope
diff --git a/aiosmtpd/main.py b/aiosmtpd/main.py
index 2366ae4..166484c 100644
--- a/aiosmtpd/main.py
+++ b/aiosmtpd/main.py
@@ -1,7 +1,6 @@
# Copyright 2014-2021 The aiosmtpd Developers
# SPDX-License-Identifier: Apache-2.0
-import asyncio
import logging
import os
import signal
@@ -16,7 +15,7 @@ from typing import Optional, Sequence, Tuple
from public import public
-from aiosmtpd import __version__
+from aiosmtpd import __version__, _get_or_new_eventloop
from aiosmtpd.smtp import DATA_SIZE_DEFAULT, SMTP
try:
@@ -252,7 +251,7 @@ def main(args: Optional[Sequence[str]] = None) -> None:
logging.basicConfig(level=logging.ERROR)
log = logging.getLogger("mail.log")
- loop = asyncio.get_event_loop()
+ loop = _get_or_new_eventloop()
if args.debug > 0:
log.setLevel(logging.INFO)
diff --git a/aiosmtpd/proxy_protocol.py b/aiosmtpd/proxy_protocol.py
index 537e522..5a41f0b 100644
--- a/aiosmtpd/proxy_protocol.py
+++ b/aiosmtpd/proxy_protocol.py
@@ -104,15 +104,12 @@ class UnknownTypeTLV(KeyError):
class AsyncReader(Protocol):
async def read(self, num_bytes: Optional[int] = None) -> bytes:
...
- return b""
async def readexactly(self, num_bytes: int) -> bytes:
...
- return b""
async def readuntil(self, until_chars: Optional[bytes] = None) -> bytes:
...
- return b""
_anoinit = partial(attr.ib, init=False)
diff --git a/aiosmtpd/qa/test_0packaging.py b/aiosmtpd/qa/test_0packaging.py
index 1f3fa6a..2c1e3d8 100644
--- a/aiosmtpd/qa/test_0packaging.py
+++ b/aiosmtpd/qa/test_0packaging.py
@@ -17,7 +17,7 @@ from packaging import version
from aiosmtpd import __version__
RE_DUNDERVER = re.compile(r"__version__\s*?=\s*?(['\"])(?P<ver>[^'\"]+)\1\s*$")
-RE_VERHEADING = re.compile(r"(?P<ver>[0-9.]+)\s*\((?P<date>[^)]+)\)")
+RE_VERHEADING = re.compile(r"(?P<ver>([0-9.]+)\S*)\s*\((?P<date>[^)]+)\)")
@pytest.fixture
diff --git a/aiosmtpd/smtp.py b/aiosmtpd/smtp.py
index cfc37e4..a977f75 100644
--- a/aiosmtpd/smtp.py
+++ b/aiosmtpd/smtp.py
@@ -34,7 +34,7 @@ from warnings import warn
import attr
from public import public
-from aiosmtpd import __version__
+from aiosmtpd import __version__, _get_or_new_eventloop
from aiosmtpd.proxy_protocol import ProxyData, get_proxy
@@ -208,7 +208,7 @@ class Envelope:
# unit test suite. In that case, this function is mocked to set the debug
# level on the loop (as if PYTHONASYNCIODEBUG=1 were set).
def make_loop() -> asyncio.AbstractEventLoop:
- return asyncio.get_event_loop()
+ return _get_or_new_eventloop()
@public
@@ -336,7 +336,7 @@ class SMTP(asyncio.StreamReaderProtocol):
self.loop = loop if loop else make_loop()
super().__init__(
asyncio.StreamReader(loop=self.loop, limit=self.line_length_limit),
- client_connected_cb=self._client_connected_cb,
+ client_connected_cb=self._cb_client_connected,
loop=self.loop)
self.event_handler = handler
assert data_size_limit is None or isinstance(data_size_limit, int)
@@ -560,7 +560,7 @@ class SMTP(asyncio.StreamReaderProtocol):
# up state.
self.transport.close()
- def _client_connected_cb(
+ def _cb_client_connected(
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
):
# This is redundant since we subclass StreamReaderProtocol, but I like
diff --git a/aiosmtpd/tests/conftest.py b/aiosmtpd/tests/conftest.py
index 6a8c3dd..0c69103 100644
--- a/aiosmtpd/tests/conftest.py
+++ b/aiosmtpd/tests/conftest.py
@@ -5,6 +5,7 @@ import asyncio
import inspect
import socket
import ssl
+import warnings
from contextlib import suppress
from functools import wraps
from smtplib import SMTP as SMTPClient
@@ -200,14 +201,20 @@ def get_handler(request: pytest.FixtureRequest) -> Callable:
@pytest.fixture
def temp_event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
- default_loop = asyncio.get_event_loop()
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ try:
+ default_loop = asyncio.get_event_loop()
+ except (DeprecationWarning, RuntimeError):
+ default_loop = None
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
#
yield new_loop
#
new_loop.close()
- asyncio.set_event_loop(default_loop)
+ if default_loop is not None:
+ asyncio.set_event_loop(default_loop)
@pytest.fixture
diff --git a/aiosmtpd/tests/test_handlers.py b/aiosmtpd/tests/test_handlers.py
index d48593c..392689d 100644
--- a/aiosmtpd/tests/test_handlers.py
+++ b/aiosmtpd/tests/test_handlers.py
@@ -816,8 +816,7 @@ class TestProxyMocked:
logger_name = "mail.debug"
caplog.set_level(logging.INFO, logger=logger_name)
client.sendmail("anne@example.com", ["bart@example.com"], self.SOURCE)
- _l1 = -1
- for _l1, rt in enumerate(reversed(caplog.record_tuples)):
+ for rt in reversed(caplog.record_tuples):
if rt == (
logger_name,
logging.INFO,
diff --git a/aiosmtpd/tests/test_misc.py b/aiosmtpd/tests/test_misc.py
new file mode 100644
index 0000000..94f1489
--- /dev/null
+++ b/aiosmtpd/tests/test_misc.py
@@ -0,0 +1,52 @@
+# Copyright 2014-2021 The aiosmtpd Developers
+# SPDX-License-Identifier: Apache-2.0
+
+"""Test other aspects of the server implementation."""
+
+import asyncio
+import warnings
+from typing import Generator, Optional
+
+import pytest
+
+from aiosmtpd import _get_or_new_eventloop
+
+
+@pytest.fixture(scope="module")
+def close_existing_loop() -> Generator[Optional[asyncio.AbstractEventLoop], None, None]:
+ loop: Optional[asyncio.AbstractEventLoop]
+ with warnings.catch_warnings():
+ warnings.filterwarnings("error")
+ try:
+ loop = asyncio.get_event_loop()
+ except (DeprecationWarning, RuntimeError):
+ loop = None
+ if loop:
+ loop.stop()
+ loop.close()
+ asyncio.set_event_loop(None)
+ yield loop
+ else:
+ yield None
+
+
+class TestInit:
+
+ def test_create_new_if_none(self, close_existing_loop):
+ old_loop = close_existing_loop
+ loop: Optional[asyncio.AbstractEventLoop]
+ loop = _get_or_new_eventloop()
+ assert loop is not None
+ assert loop is not old_loop
+ assert isinstance(loop, asyncio.AbstractEventLoop)
+
+ def test_not_create_new_if_exist(self, close_existing_loop):
+ old_loop = close_existing_loop
+ loop: Optional[asyncio.AbstractEventLoop]
+ loop = asyncio.new_event_loop()
+ assert loop is not old_loop
+ asyncio.set_event_loop(loop)
+ ret_loop = _get_or_new_eventloop()
+ assert ret_loop is not old_loop
+ assert ret_loop == loop
+ assert ret_loop is loop
diff --git a/aiosmtpd/tests/test_proxyprotocol.py b/aiosmtpd/tests/test_proxyprotocol.py
index 949e032..02ec5f3 100644
--- a/aiosmtpd/tests/test_proxyprotocol.py
+++ b/aiosmtpd/tests/test_proxyprotocol.py
@@ -1112,7 +1112,6 @@ class TestHandlerAcceptReject:
else:
oper = operator.eq
expect = pytest.raises(SMTPServerDisconnected)
- oper = operator.ne if handler_retval else operator.eq
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect(Global.SrvAddr)
sock.sendall(handshake)
diff --git a/aiosmtpd/tests/test_server.py b/aiosmtpd/tests/test_server.py
index bda6d3b..656e963 100644
--- a/aiosmtpd/tests/test_server.py
+++ b/aiosmtpd/tests/test_server.py
@@ -403,7 +403,7 @@ class TestUnthreaded:
def starter(loop: asyncio.AbstractEventLoop):
nonlocal thread
thread = Thread(target=_runner, args=(loop,))
- thread.setDaemon(True)
+ thread.daemon = True
thread.start()
catchup_delay()
diff --git a/debian/changelog b/debian/changelog
index 72e5698..94e6c00 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+python-aiosmtpd (1.4.4.post2-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+ * Drop patch 0003-Remove-imported-images-from-the-web-for-privacy.patch,
+ present upstream.
+
+ -- Debian Janitor <janitor@jelmer.uk> Sat, 03 Jun 2023 11:58:42 -0000
+
python-aiosmtpd (1.4.3-1.1) unstable; urgency=medium
* Non-maintainer upload.
diff --git a/debian/patches/0001-Skip-a-git-version-test.patch b/debian/patches/0001-Skip-a-git-version-test.patch
index 14cda15..2821e80 100644
--- a/debian/patches/0001-Skip-a-git-version-test.patch
+++ b/debian/patches/0001-Skip-a-git-version-test.patch
@@ -7,10 +7,10 @@ Forwarded: not-needed
aiosmtpd/qa/test_0packaging.py | 1 +
1 file changed, 1 insertion(+)
-diff --git a/aiosmtpd/qa/test_0packaging.py b/aiosmtpd/qa/test_0packaging.py
-index 1f3fa6a..cb2f5c1 100644
---- a/aiosmtpd/qa/test_0packaging.py
-+++ b/aiosmtpd/qa/test_0packaging.py
+Index: python-aiosmtpd.git/aiosmtpd/qa/test_0packaging.py
+===================================================================
+--- python-aiosmtpd.git.orig/aiosmtpd/qa/test_0packaging.py
++++ python-aiosmtpd.git/aiosmtpd/qa/test_0packaging.py
@@ -33,6 +33,7 @@ class TestVersion:
), "Version number must comply with PEP-440"
diff --git a/debian/patches/0002-Drop-sphinx-autofixture-extension-requirement.patch b/debian/patches/0002-Drop-sphinx-autofixture-extension-requirement.patch
index ae6a096..9fd0da0 100644
--- a/debian/patches/0002-Drop-sphinx-autofixture-extension-requirement.patch
+++ b/debian/patches/0002-Drop-sphinx-autofixture-extension-requirement.patch
@@ -7,10 +7,10 @@ Forwarded: not-needed
aiosmtpd/docs/conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
-diff --git a/aiosmtpd/docs/conf.py b/aiosmtpd/docs/conf.py
-index 689e4a7..ab4495b 100644
---- a/aiosmtpd/docs/conf.py
-+++ b/aiosmtpd/docs/conf.py
+Index: python-aiosmtpd.git/aiosmtpd/docs/conf.py
+===================================================================
+--- python-aiosmtpd.git.orig/aiosmtpd/docs/conf.py
++++ python-aiosmtpd.git/aiosmtpd/docs/conf.py
@@ -60,7 +60,7 @@ extensions = [
"sphinx.ext.intersphinx",
"sphinx.ext.autodoc",
diff --git a/debian/patches/0003-Remove-imported-images-from-the-web-for-privacy.patch b/debian/patches/0003-Remove-imported-images-from-the-web-for-privacy.patch
deleted file mode 100644
index 1124faa..0000000
--- a/debian/patches/0003-Remove-imported-images-from-the-web-for-privacy.patch
+++ /dev/null
@@ -1,97 +0,0 @@
-From: =?utf-8?q?Pierre-Elliott_B=C3=A9cue?= <peb@debian.org>
-Date: Mon, 18 Oct 2021 23:22:10 +0200
-Subject: Remove imported images from the web for privacy
-
-Forwarded: not-needed
----
- DESCRIPTION.rst | 36 ------------------------------------
- README.rst | 29 -----------------------------
- 2 files changed, 65 deletions(-)
-
-diff --git a/DESCRIPTION.rst b/DESCRIPTION.rst
-index f341b0a..a82b431 100644
---- a/DESCRIPTION.rst
-+++ b/DESCRIPTION.rst
-@@ -7,42 +7,6 @@
- | |GH Release| |_| |GH PRs| |_| |GH LastCommit|
- |
-
--.. |_| unicode:: 0xA0
-- :trim:
--.. |github license| image:: https://img.shields.io/github/license/aio-libs/aiosmtpd?logo=Open+Source+Initiative&logoColor=0F0
-- :target: https://github.com/aio-libs/aiosmtpd/blob/master/LICENSE
-- :alt: Project License on GitHub
--.. |PyPI Version| image:: https://img.shields.io/pypi/v/aiosmtpd?logo=pypi&logoColor=yellow
-- :target: https://pypi.org/project/aiosmtpd/
-- :alt: PyPI Package
--.. |PyPI Python| image:: https://img.shields.io/pypi/pyversions/aiosmtpd?logo=python&logoColor=yellow
-- :target: https://pypi.org/project/aiosmtpd/
-- :alt: Supported Python Versions
--.. .. 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://img.shields.io/readthedocs/aiosmtpd?logo=Read+the+Docs
-- :target: https://aiosmtpd.readthedocs.io/en/latest/?badge=latest
-- :alt: Documentation Status
--.. .. Do NOT include the Discourse badge!
--.. .. Below are badges just for PyPI
--.. |GH Release| image:: https://img.shields.io/github/v/release/aio-libs/aiosmtpd?logo=github
-- :target: https://github.com/aio-libs/aiosmtpd/releases
-- :alt: GitHub latest release
--.. |GH PRs| image:: https://img.shields.io/github/issues-pr/aio-libs/aiosmtpd?logo=GitHub
-- :target: https://github.com/aio-libs/aiosmtpd/pulls
-- :alt: GitHub pull requests
--.. |GH LastCommit| image:: https://img.shields.io/github/last-commit/aio-libs/aiosmtpd?logo=GitHub
-- :target: https://github.com/aio-libs/aiosmtpd/commits/master
-- :alt: GitHub last commit
--
- This is a server for SMTP and related MTA protocols,
- similar in utility to the standard library's |smtpd.py|_ module,
- but rewritten to be based on ``asyncio`` for Python 3.7+.
-diff --git a/README.rst b/README.rst
-index 9cca496..d60d251 100644
---- a/README.rst
-+++ b/README.rst
-@@ -7,35 +7,6 @@
- |
- | |Discourse|
-
--.. |_| unicode:: 0xA0
-- :trim:
--.. |github license| image:: https://img.shields.io/github/license/aio-libs/aiosmtpd?logo=Open+Source+Initiative&logoColor=0F0
-- :target: https://github.com/aio-libs/aiosmtpd/blob/master/LICENSE
-- :alt: Project License on GitHub
--.. |PyPI Version| image:: https://img.shields.io/pypi/v/aiosmtpd?logo=pypi&logoColor=yellow
-- :target: https://pypi.org/project/aiosmtpd/
-- :alt: PyPI Package
--.. |PyPI Python| image:: https://img.shields.io/pypi/pyversions/aiosmtpd?logo=python&logoColor=yellow
-- :target: https://pypi.org/project/aiosmtpd/
-- :alt: Supported Python Versions
--.. .. 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://img.shields.io/readthedocs/aiosmtpd?logo=Read+the+Docs&logoColor=white
-- :target: https://aiosmtpd.readthedocs.io/en/latest/
-- :alt: Documentation Status
--.. .. 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
-
- The Python standard library includes a basic |SMTP|_ server in the |smtpd|_ module,
- based on the old asynchronous libraries |asyncore|_ and |asynchat|_.
diff --git a/debian/patches/0004-Replace-a-dynamic-date-in-copyright-by-a-static-one.patch b/debian/patches/0004-Replace-a-dynamic-date-in-copyright-by-a-static-one.patch
index 3a38937..496898f 100644
--- a/debian/patches/0004-Replace-a-dynamic-date-in-copyright-by-a-static-one.patch
+++ b/debian/patches/0004-Replace-a-dynamic-date-in-copyright-by-a-static-one.patch
@@ -7,10 +7,10 @@ Necessary for reproducibility
aiosmtpd/docs/conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
-diff --git a/aiosmtpd/docs/conf.py b/aiosmtpd/docs/conf.py
-index ab4495b..b4fb709 100644
---- a/aiosmtpd/docs/conf.py
-+++ b/aiosmtpd/docs/conf.py
+Index: python-aiosmtpd.git/aiosmtpd/docs/conf.py
+===================================================================
+--- python-aiosmtpd.git.orig/aiosmtpd/docs/conf.py
++++ python-aiosmtpd.git/aiosmtpd/docs/conf.py
@@ -83,7 +83,7 @@ master_doc = "index"
author = "The aiosmtpd Developers"
project = "aiosmtpd"
diff --git a/debian/patches/series b/debian/patches/series
index debdae2..83f95b4 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,4 +1,3 @@
0001-Skip-a-git-version-test.patch
0002-Drop-sphinx-autofixture-extension-requirement.patch
-0003-Remove-imported-images-from-the-web-for-privacy.patch
0004-Replace-a-dynamic-date-in-copyright-by-a-static-one.patch
diff --git a/examples/authenticated_relayer/server.py b/examples/authenticated_relayer/server.py
index fd4a7b4..f00b602 100644
--- a/examples/authenticated_relayer/server.py
+++ b/examples/authenticated_relayer/server.py
@@ -94,9 +94,10 @@ if __name__ == '__main__':
print(f"Please create {DB_AUTH} first using make_user_db.py")
sys.exit(1)
logging.basicConfig(level=logging.DEBUG)
- loop = asyncio.get_event_loop()
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
loop.create_task(amain())
try:
loop.run_forever()
except KeyboardInterrupt:
- pass
+ print("User abort indicated")
diff --git a/examples/basic/server.py b/examples/basic/server.py
index ab91a13..fcb572a 100644
--- a/examples/basic/server.py
+++ b/examples/basic/server.py
@@ -15,9 +15,10 @@ async def amain(loop):
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
- loop = asyncio.get_event_loop()
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
loop.create_task(amain(loop=loop))
try:
loop.run_forever()
except KeyboardInterrupt:
- pass
+ print("User abort indicated")
diff --git a/pyproject.toml b/pyproject.toml
index e067d36..8731e60 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,6 +35,8 @@ source = [
[tool.coverage.coverage_conditional_plugin.rules]
# Here we specify our pragma rules:
+py-lt-312 = "sys_version_info < (3, 12)"
+py-lt-310 = "sys_version_info < (3, 10)"
py-ge-38 = "sys_version_info >= (3, 8)"
py-lt-38 = "sys_version_info < (3, 8)"
py-gt-36 = "sys_version_info > (3, 6)"
diff --git a/release.py b/release.py
index d0c3a4a..00c2e13 100755
--- a/release.py
+++ b/release.py
@@ -12,18 +12,26 @@ import time
from functools import partial
from pathlib import Path
-from aiosmtpd import __version__ as version
+from packaging import version
+
+from aiosmtpd import __version__ as ver_str
printfl = partial(print, flush=True)
run_hidden = partial(subprocess.run, stdout=subprocess.PIPE)
+result = run_hidden(shlex.split("git status --porcelain"))
+if result.stdout:
+ print("git is not clean!")
+ print("Please commit/shelf first before releasing!")
+ sys.exit(1)
+
TWINE_CONFIG = Path(os.environ.get("TWINE_CONFIG", "~/.pypirc")).expanduser()
TWINE_REPO = os.environ.get("TWINE_REPOSITORY", "aiosmtpd")
UPSTREAM_REMOTE = os.environ.get("UPSTREAM_REMOTE", "upstream")
GPG_SIGNING_ID = os.environ.get("GPG_SIGNING_ID")
DISTFILES = [
- f"dist/aiosmtpd-{version}.tar.gz",
- f"dist/aiosmtpd-{version}-py3-none-any.whl",
+ f"dist/aiosmtpd-{ver_str}.tar.gz",
+ f"dist/aiosmtpd-{ver_str}-py3-none-any.whl",
]
printfl("Updating release toolkit first...", end="")
@@ -48,20 +56,20 @@ UPSTREAM_REMOTE = {UPSTREAM_REMOTE}
GPG_SIGNING_ID = {GPG_SIGNING_ID or 'None'}
"""
)
-choice = input(f"Release aiosmtpd {version} - correct? [y/N]: ")
+choice = input(f"Release aiosmtpd {ver_str} - correct? [y/N]: ")
if choice.lower() not in ("y", "yes"):
sys.exit("Release aborted")
newsfile = Path(".") / "aiosmtpd" / "docs" / "NEWS.rst"
with newsfile.open("rt") as fin:
- want = re.compile("^" + re.escape(version) + r"\s*\(\d{4}-\d\d-\d\d\)")
+ want = re.compile("^" + re.escape(ver_str) + r"\s*\(\d{4}-\d\d-\d\d\)")
for ln in fin:
m = want.match(ln)
if not m:
continue
break
else:
- print(f"ERROR: I found no datestamped entry for {version} in NEWS.rst!")
+ print(f"ERROR: I found no datestamped entry for {ver_str} in NEWS.rst!")
sys.exit(1)
if not GPG_SIGNING_ID:
@@ -99,7 +107,16 @@ try:
# Assuming twine is installed.
print("### twine check")
subprocess.run(["twine", "check"] + DISTFILES, check=True)
+except subprocess.CalledProcessError as e:
+ print("ERROR: Last step returned exitcode != 0")
+ sys.exit(e.returncode)
+choice = input("Ready to upload to PyPI? [y/N]: ")
+if choice.casefold() not in ("y", "yes"):
+ print("Okay.")
+ sys.exit(0)
+
+try:
# You should have an aiosmtpd bit setup in your ~/.pypirc - for twine
twine_up = f"twine upload --config-file {TWINE_CONFIG} -r {TWINE_REPO}".split()
if GPG_SIGNING_ID:
@@ -134,10 +151,20 @@ if has_verify:
# Only tag when we've actually built and uploaded. If something goes wrong
# we may need the tag somewhere else!
choice = input("tag and push? [y/N]: ")
-if choice.lower() not in ("y", "yes"):
- pass
-else:
+if choice.lower() in ("y", "yes"):
# The annotation information should come from the changelog
- subprocess.run(["git", "tag", "-a", version])
+ subprocess.run(["git", "tag", "-a", ver_str])
# And now push the tag, of course.
- subprocess.run(["git", "push", "--atomic", UPSTREAM_REMOTE, "master", version])
+ subprocess.run(["git", "push", "--atomic", UPSTREAM_REMOTE, "master", ver_str])
+ vv = version.parse(ver_str)
+ new_ver = version.Version(f"{vv.major}.{vv.minor}.{vv.micro + 1}a0")
+ print("\u2591\u2592\u2593\u2588 IMPORTANT \u2588\u2593\u2592\u2591")
+ print(
+ f"Now that version {ver_str} has been tagged and pushed, "
+ f"you should bump the code to a new version."
+ )
+ print(
+ f"Suggested version is '{new_ver}'. Please do a grep for "
+ f"the old version and perform changes as necessary."
+ )
+ print("(Also remember to add a news stub in NEWS.rst if you bump the version.)")
diff --git a/tox.ini b/tox.ini
index f4fcf92..211e78d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,15 +4,7 @@ envlist = qa, static, docs, py{37,38,39,310,311,py3}-{nocov,cov,diffcov}
skip_missing_interpreters = True
[testenv]
-# One virtualenv per Python version
-envdir =
- py37: {toxworkdir}/3.7
- py38: {toxworkdir}/3.8
- py39: {toxworkdir}/3.9
- py310: {toxworkdir}/3.10
- py311: {toxworkdir}/3.11
- pypy3: {toxworkdir}/pypy3
- py: {toxworkdir}/py
+envdir = {toxworkdir}/{envname}
commands =
python housekeep.py prep
# Bandit is not needed on diffcov, and seems to be incompatible with 310
@@ -27,20 +19,20 @@ commands =
#sitepackages = True
usedevelop = True
deps =
- # do NOT make these conditional, that way we can reuse same envdir for nocov+cov+diffcov
bandit
colorama
- coverage[toml]
- coverage-conditional-plugin
packaging
pytest >= 6.0 # Require >= 6.0 for pyproject.toml support (PEP 517)
- pytest-cov
pytest-mock
pytest-print
pytest-profiling
pytest-sugar
py # needed for pytest-sugar as it doesn't declare dependency on it.
- diff_cover
+ !nocov: coverage>=7.0.1
+ !nocov: coverage[toml]
+ !nocov: coverage-conditional-plugin
+ !nocov: pytest-cov
+ diffcov: diff_cover
setenv =
cov: COVERAGE_FILE={toxinidir}/_dump/.coverage
nocov: PYTHONASYNCIODEBUG=1
@@ -50,6 +42,8 @@ setenv =
py310: INTERP=py310
py311: INTERP=py311
pypy3: INTERP=pypy3
+ pypy37: INTERP=pypy37
+ pypy38: INTERP=pypy38
py: INTERP=py
passenv =
PYTHON*