diff --git a/.gitignore b/.gitignore index 0315c5bf..f4d608a8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,15 +26,6 @@ sdist/ .installed.cfg *.egg -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Allow RPM SPEC files despite pyInstaller ignore -!packaging/redhat/*.spec - # Installer logs pip-log.txt pip-delete-this-directory.txt diff --git a/.travis.yml b/.travis.yml index 8088d5d0..505db72d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: python -dist: xenial +dist: focal addons: apt: @@ -11,19 +11,23 @@ matrix: include: - python: "3.6" env: TOXENV=py36 - - python: "3.7.7" + - python: "3.7" env: TOXENV=py37 - python: "3.8" env: TOXENV=py38 - python: "3.9" env: TOXENV=py39 - - python: "3.9-dev" - env: TOXENV=py39-dev + - python: "3.10" + env: TOXENV=py310 # PyPy Environments - - python: "pypy2.7-6.0" - env: TOXENV=pypy - - python: "pypy3.5-7.0" - env: TOXENV=pypy3 + - python: "pypy3.6-7.3.3" + env: TOXENV=pypy36 + - python: "pypy3.7-7.3.9" + env: TOXENV=pypy37 + - python: "pypy3.8-7.3.9" + env: TOXENV=pypy38 + - python: "pypy3.9-7.3.9" + env: TOXENV=pypy39 # An extra environment where additional packages are not installed - python: "3.9" env: @@ -31,20 +35,27 @@ matrix: install: - pip install babel - # upgrade tox, pip, and virtualenv so Python 3.6 will build crytography: - # https://travis-ci.community/t/pip-install-cryptography-fails-on-py36/11233 - - pip install -U tox pip virtualenv + + # Use up-to-date versions of tox, pip, virtualenv, and wheel. + - pip install --upgrade tox pip virtualenv wheel + + # cryptography 3.3 is the last one not needing a Rust toolchain. Let's use it for PyPy. + - if [[ $TOXENV == 'pypy'* ]]; then pip install "cryptography<3.4"; fi + + # Install project dependencies. - pip install codecov - pip install -r dev-requirements.txt - pip install -r requirements.txt + # bare installs do not include extra package dependencies - if [[ $TOXENV != 'bare' ]]; then pip install -r all-plugin-requirements.txt; fi # pypy and bare installs do not include dbus-python - if [[ $TOXENV != 'bare' ]] && [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then travis_retry pip install dbus-python; fi - # Python 3.7 importlib-metadata becomes incompatible with flake8 unless we use - # a version that still supports EntryPoints.get(); tox.ini updated to not call flake; this was - # the only way around the Travis CI Builder issues - - if [[ $TOXENV == 'py37' ]]; then pip uninstall --yes flake8; fi + + # Fix/workaround: Python 3.7 importlib-metadata becomes incompatible with flake8, + # unless we use a version that still supports EntryPoints.get(). + # `tox.ini` has been updated to not call flake8 on Python 3.7. + - if [[ $TOXENV == 'py37' || $TOXENV == 'pypy37' ]]; then pip uninstall --yes flake8; fi # run tests script: diff --git a/Dockerfile.py27 b/Dockerfile.py27 deleted file mode 100644 index f9758239..00000000 --- a/Dockerfile.py27 +++ /dev/null @@ -1,14 +0,0 @@ -# Base -FROM python:2.7-buster -RUN apt-get update && \ - apt-get install -y libdbus-1-dev build-essential musl-dev bash -RUN pip install dbus-python - -# Apprise Setup -COPY . /apprise -ENV PYTHONPATH /apprise -WORKDIR /apprise -RUN pip install -r requirements.txt -r dev-requirements.txt - -# Catalog Construction and Wheel Building -RUN python setup.py compile_catalog bdist_wheel diff --git a/apprise/Apprise.py b/apprise/Apprise.py index c0ccb900..39a1ff0a 100644 --- a/apprise/Apprise.py +++ b/apprise/Apprise.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import os -import six from itertools import chain from . import common from .conversion import convert_between @@ -44,13 +43,13 @@ from .plugins.NotifyBase import NotifyBase from . import plugins from . import __version__ -# Python v3+ support code made importable so it can remain backwards +# Python v3+ support code made importable, so it can remain backwards # compatible with Python v2 +# TODO: Review after dropping support for Python 2. from . import py3compat -ASYNCIO_SUPPORT = not six.PY2 -class Apprise(object): +class Apprise: """ Our Notification Manager @@ -124,7 +123,7 @@ class Apprise(object): # Prepare our Asset Object asset = asset if isinstance(asset, AppriseAsset) else AppriseAsset() - if isinstance(url, six.string_types): + if isinstance(url, str): # Acquire our url tokens results = plugins.url_to_dict( url, secure_logging=asset.secure_logging) @@ -247,7 +246,7 @@ class Apprise(object): # prepare default asset asset = self.asset - if isinstance(servers, six.string_types): + if isinstance(servers, str): # build our server list servers = parse_urls(servers) if len(servers) == 0: @@ -275,7 +274,7 @@ class Apprise(object): self.servers.append(_server) continue - elif not isinstance(_server, (six.string_types, dict)): + elif not isinstance(_server, (str, dict)): logger.error( "An invalid notification (type={}) was specified.".format( type(_server))) @@ -306,7 +305,7 @@ class Apprise(object): def find(self, tag=common.MATCH_ALL_TAG, match_always=True): """ - Returns an list of all servers matching against the tag specified. + Returns a list of all servers matching against the tag specified. """ @@ -347,14 +346,14 @@ class Apprise(object): body_format=None, tag=common.MATCH_ALL_TAG, match_always=True, attach=None, interpret_escapes=None): """ - Send a notification to all of the plugins previously loaded. + Send a notification to all the plugins previously loaded. If the body_format specified is NotifyFormat.MARKDOWN, it will be converted to HTML if the Notification type expects this. if the tag is specified (either a string or a set/list/tuple of strings), then only the notifications flagged with that - tagged value are notified. By default all added services + tagged value are notified. By default, all added services are notified (tag=MATCH_ALL_TAG) This function returns True if all notifications were successfully @@ -363,60 +362,33 @@ class Apprise(object): simply having empty configuration files that were read. Attach can contain a list of attachment URLs. attach can also be - represented by a an AttachBase() (or list of) object(s). This + represented by an AttachBase() (or list of) object(s). This identifies the products you wish to notify Set interpret_escapes to True if you want to pre-escape a string such as turning a \n into an actual new line, etc. """ - if ASYNCIO_SUPPORT: - return py3compat.asyncio.tosync( - self.async_notify( - body, title, - notify_type=notify_type, body_format=body_format, - tag=tag, match_always=match_always, attach=attach, - interpret_escapes=interpret_escapes, - ), - debug=self.debug - ) - - else: - try: - results = list( - self._notifyall( - Apprise._notifyhandler, - body, title, - notify_type=notify_type, body_format=body_format, - tag=tag, attach=attach, - interpret_escapes=interpret_escapes, - ) - ) - - except TypeError: - # No notifications sent, and there was an internal error. - return False - - else: - if len(results) > 0: - # All notifications sent, return False if any failed. - return all(results) - - else: - # No notifications sent. - return None + return py3compat.asyncio.tosync( + self.async_notify( + body, title, + notify_type=notify_type, body_format=body_format, + tag=tag, match_always=match_always, attach=attach, + interpret_escapes=interpret_escapes, + ), + debug=self.debug + ) def async_notify(self, *args, **kwargs): """ - Send a notification to all of the plugins previously loaded, for + Send a notification to all the plugins previously loaded, for asynchronous callers. This method is an async method that should be awaited on, even if it is missing the async keyword in its signature. (This is omitted to preserve syntax compatibility with Python 2.) - The arguments are identical to those of Apprise.notify(). This method - is not available in Python 2. - """ + The arguments are identical to those of Apprise.notify(). + """ try: coroutines = list( self._notifyall( @@ -477,7 +449,7 @@ class Apprise(object): tag=common.MATCH_ALL_TAG, match_always=True, attach=None, interpret_escapes=None): """ - Creates notifications for all of the plugins loaded. + Creates notifications for all the plugins loaded. Returns a generator that calls handler for each notification. The first and only argument supplied to handler is the server, and the keyword @@ -496,23 +468,11 @@ class Apprise(object): raise TypeError(msg) try: - if six.PY2: - # Python 2.7 encoding support isn't the greatest, so we try - # to ensure that we're ALWAYS dealing with unicode characters - # prior to entrying the next part. This is especially required - # for Markdown support - if title and isinstance(title, str): # noqa: F821 - title = title.decode(self.asset.encoding) - - if body and isinstance(body, str): # noqa: F821 - body = body.decode(self.asset.encoding) + if title and isinstance(title, bytes): + title = title.decode(self.asset.encoding) - else: # Python 3+ - if title and isinstance(title, bytes): # noqa: F821 - title = title.decode(self.asset.encoding) - - if body and isinstance(body, bytes): # noqa: F821 - body = body.decode(self.asset.encoding) + if body and isinstance(body, bytes): + body = body.decode(self.asset.encoding) except UnicodeDecodeError: msg = 'The content passed into Apprise was not of encoding ' \ @@ -580,43 +540,12 @@ class Apprise(object): .encode('ascii', 'backslashreplace')\ .decode('unicode-escape') - except UnicodeDecodeError: # pragma: no cover - # This occurs using a very old verion of Python 2.7 - # such as the one that ships with CentOS/RedHat 7.x - # (v2.7.5). - conversion_body_map[server.notify_format] = \ - conversion_body_map[server.notify_format] \ - .decode('string_escape') - - conversion_title_map[server.notify_format] = \ - conversion_title_map[server.notify_format] \ - .decode('string_escape') - except AttributeError: # Must be of string type msg = 'Failed to escape message body' logger.error(msg) raise TypeError(msg) - if six.PY2: - # Python 2.7 strings must be encoded as utf-8 for - # consistency across all platforms - if conversion_body_map[server.notify_format] and \ - isinstance( - conversion_body_map[server.notify_format], - unicode): # noqa: F821 - conversion_body_map[server.notify_format] = \ - conversion_body_map[server.notify_format]\ - .encode('utf-8') - - if conversion_title_map[server.notify_format] and \ - isinstance( - conversion_title_map[server.notify_format], - unicode): # noqa: F821 - conversion_title_map[server.notify_format] = \ - conversion_title_map[server.notify_format]\ - .encode('utf-8') - yield handler( server, body=conversion_body_map[server.notify_format], @@ -669,12 +598,12 @@ class Apprise(object): # Standard protocol(s) should be None or a tuple protocols = getattr(plugin, 'protocol', None) - if isinstance(protocols, six.string_types): + if isinstance(protocols, str): protocols = (protocols, ) # Secure protocol(s) should be None or a tuple secure_protocols = getattr(plugin, 'secure_protocol', None) - if isinstance(secure_protocols, six.string_types): + if isinstance(secure_protocols, str): secure_protocols = (secure_protocols, ) # Add our protocol details to our content @@ -779,15 +708,8 @@ class Apprise(object): def __bool__(self): """ - Allows the Apprise object to be wrapped in an Python 3.x based 'if - statement'. True is returned if at least one service has been loaded. - """ - return len(self) > 0 - - def __nonzero__(self): - """ - Allows the Apprise object to be wrapped in an Python 2.x based 'if - statement'. True is returned if at least one service has been loaded. + Allows the Apprise object to be wrapped in an 'if statement'. + True is returned if at least one service has been loaded. """ return len(self) > 0 @@ -807,7 +729,3 @@ class Apprise(object): """ return sum([1 if not isinstance(s, (ConfigBase, AppriseConfig)) else len(s.servers()) for s in self.servers]) - - -if six.PY2: - del Apprise.async_notify diff --git a/apprise/Apprise.pyi b/apprise/Apprise.pyi index 919d370d..5a34c9c6 100644 --- a/apprise/Apprise.pyi +++ b/apprise/Apprise.pyi @@ -58,6 +58,5 @@ class Apprise: def pop(self, index: int) -> ConfigBase: ... def __getitem__(self, index: int) -> ConfigBase: ... def __bool__(self) -> bool: ... - def __nonzero__(self) -> bool: ... def __iter__(self) -> Iterator[ConfigBase]: ... def __len__(self) -> int: ... \ No newline at end of file diff --git a/apprise/AppriseAsset.py b/apprise/AppriseAsset.py index 58e36c90..80bd0656 100644 --- a/apprise/AppriseAsset.py +++ b/apprise/AppriseAsset.py @@ -33,7 +33,7 @@ from .common import NotifyType from .utils import module_detection -class AppriseAsset(object): +class AppriseAsset: """ Provides a supplimentary class that can be used to provide extra information and details that can be used by Apprise such as providing @@ -107,8 +107,8 @@ class AppriseAsset(object): # - NotifyFormat.HTML # - None # - # If no format is specified (hence None), then no special pre-formating - # actions will take place during a notificaton. This has been and always + # If no format is specified (hence None), then no special pre-formatting + # actions will take place during a notification. This has been and always # will be the default. body_format = None diff --git a/apprise/AppriseAttachment.py b/apprise/AppriseAttachment.py index bbb37d69..b808cfae 100644 --- a/apprise/AppriseAttachment.py +++ b/apprise/AppriseAttachment.py @@ -23,8 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six - from . import attachment from . import URLBase from .AppriseAsset import AppriseAsset @@ -35,7 +33,7 @@ from .common import ATTACHMENT_SCHEMA_MAP from .utils import GET_SCHEMA_RE -class AppriseAttachment(object): +class AppriseAttachment: """ Our Apprise Attachment File Manager @@ -143,7 +141,7 @@ class AppriseAttachment(object): self.attachments.append(attachments) return True - elif isinstance(attachments, six.string_types): + elif isinstance(attachments, str): # Save our path attachments = (attachments, ) @@ -162,7 +160,7 @@ class AppriseAttachment(object): return_status = False continue - if isinstance(_attachment, six.string_types): + if isinstance(_attachment, str): logger.debug("Loading attachment: {}".format(_attachment)) # Instantiate ourselves an object, this function throws or # returns None if it fails @@ -296,15 +294,8 @@ class AppriseAttachment(object): def __bool__(self): """ - Allows the Apprise object to be wrapped in an Python 3.x based 'if - statement'. True is returned if at least one service has been loaded. - """ - return True if self.attachments else False - - def __nonzero__(self): - """ - Allows the Apprise object to be wrapped in an Python 2.x based 'if - statement'. True is returned if at least one service has been loaded. + Allows the Apprise object to be wrapped in an 'if statement'. + True is returned if at least one service has been loaded. """ return True if self.attachments else False diff --git a/apprise/AppriseAttachment.pyi b/apprise/AppriseAttachment.pyi index d68eccc1..a28acb14 100644 --- a/apprise/AppriseAttachment.pyi +++ b/apprise/AppriseAttachment.pyi @@ -33,6 +33,5 @@ class AppriseAttachment: def pop(self, index: int = ...) -> AttachBase: ... def __getitem__(self, index: int) -> AttachBase: ... def __bool__(self) -> bool: ... - def __nonzero__(self) -> bool: ... def __iter__(self) -> Iterator[AttachBase]: ... def __len__(self) -> int: ... \ No newline at end of file diff --git a/apprise/AppriseConfig.py b/apprise/AppriseConfig.py index 5da17589..f92d31d1 100644 --- a/apprise/AppriseConfig.py +++ b/apprise/AppriseConfig.py @@ -23,8 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six - from . import config from . import ConfigBase from . import CONFIG_FORMATS @@ -37,7 +35,7 @@ from .utils import is_exclusive_match from .logger import logger -class AppriseConfig(object): +class AppriseConfig: """ Our Apprise Configuration File Manager @@ -169,7 +167,7 @@ class AppriseConfig(object): self.configs.append(configs) return True - elif isinstance(configs, six.string_types): + elif isinstance(configs, str): # Save our path configs = (configs, ) @@ -187,7 +185,7 @@ class AppriseConfig(object): self.configs.append(_config) continue - elif not isinstance(_config, six.string_types): + elif not isinstance(_config, str): logger.warning( "An invalid configuration (type={}) was specified.".format( type(_config))) @@ -241,7 +239,7 @@ class AppriseConfig(object): # prepare default asset asset = self.asset - if not isinstance(content, six.string_types): + if not isinstance(content, str): logger.warning( "An invalid configuration (type={}) was specified.".format( type(content))) @@ -432,15 +430,8 @@ class AppriseConfig(object): def __bool__(self): """ - Allows the Apprise object to be wrapped in an Python 3.x based 'if - statement'. True is returned if at least one service has been loaded. - """ - return True if self.configs else False - - def __nonzero__(self): - """ - Allows the Apprise object to be wrapped in an Python 2.x based 'if - statement'. True is returned if at least one service has been loaded. + Allows the Apprise object to be wrapped in an 'if statement'. + True is returned if at least one service has been loaded. """ return True if self.configs else False diff --git a/apprise/AppriseConfig.pyi b/apprise/AppriseConfig.pyi index 36fa9c06..9ea819ac 100644 --- a/apprise/AppriseConfig.pyi +++ b/apprise/AppriseConfig.pyi @@ -44,6 +44,5 @@ class AppriseConfig: def pop(self, index: int = ...) -> ConfigBase: ... def __getitem__(self, index: int) -> ConfigBase: ... def __bool__(self) -> bool: ... - def __nonzero__(self) -> bool: ... def __iter__(self) -> Iterator[ConfigBase]: ... def __len__(self) -> int: ... \ No newline at end of file diff --git a/apprise/AppriseLocale.py b/apprise/AppriseLocale.py index 714e1180..9da8467b 100644 --- a/apprise/AppriseLocale.py +++ b/apprise/AppriseLocale.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import ctypes import locale import contextlib @@ -52,18 +51,11 @@ try: except ImportError: # gettext isn't available; no problem, just fall back to using # the library features without multi-language support. - try: - # Python v2.7 - import __builtin__ - __builtin__.__dict__['_'] = lambda x: x # pragma: no branch + import builtins + builtins.__dict__['_'] = lambda x: x # pragma: no branch - except ImportError: - # Python v3.4+ - import builtins - builtins.__dict__['_'] = lambda x: x # pragma: no branch - -class LazyTranslation(object): +class LazyTranslation: """ Doesn't translate anything until str() or unicode() references are made. @@ -89,7 +81,7 @@ def gettext_lazy(text): return LazyTranslation(text=text) -class AppriseLocale(object): +class AppriseLocale: """ A wrapper class to gettext so that we can manipulate multiple lanaguages on the fly if required. @@ -186,7 +178,7 @@ class AppriseLocale(object): """ # We want to only use the 2 character version of this language # hence en_CA becomes en, en_US becomes en. - if not isinstance(lang, six.string_types): + if not isinstance(lang, str): if detect_fallback is False: # no detection enabled; we're done return None diff --git a/apprise/URLBase.py b/apprise/URLBase.py index b9407e3c..eb4a379e 100644 --- a/apprise/URLBase.py +++ b/apprise/URLBase.py @@ -24,21 +24,13 @@ # THE SOFTWARE. import re -import six from .logger import logger from time import sleep from datetime import datetime from xml.sax.saxutils import escape as sax_escape -try: - # Python 2.7 - from urllib import unquote as _unquote - from urllib import quote as _quote - -except ImportError: - # Python 3.x - from urllib.parse import unquote as _unquote - from urllib.parse import quote as _quote +from urllib.parse import unquote as _unquote +from urllib.parse import quote as _quote from .AppriseLocale import gettext_lazy as _ from .AppriseAsset import AppriseAsset @@ -52,7 +44,7 @@ from .utils import parse_phone_no PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+') -class PrivacyMode(object): +class PrivacyMode: # Defines different privacy modes strings can be printed as # Astrisk sets 4 of them: e.g. **** # This is used for passwords @@ -77,7 +69,7 @@ HTML_LOOKUP = { } -class URLBase(object): +class URLBase: """ This is the base class for all URL Manipulation """ @@ -345,7 +337,7 @@ class URLBase(object): Returns: str: The escaped html """ - if not isinstance(html, six.string_types) or not html: + if not isinstance(html, str) or not html: return '' # Escape HTML @@ -369,7 +361,7 @@ class URLBase(object): encoding and errors parameters specify how to decode percent-encoded sequences. - Wrapper to Python's unquote while remaining compatible with both + Wrapper to Python's `unquote` while remaining compatible with both Python 2 & 3 since the reference to this function changed between versions. @@ -388,20 +380,14 @@ class URLBase(object): if not content: return '' - try: - # Python v3.x - return _unquote(content, encoding=encoding, errors=errors) - - except TypeError: - # Python v2.7 - return _unquote(content) + return _unquote(content, encoding=encoding, errors=errors) @staticmethod def quote(content, safe='/', encoding=None, errors=None): """ Replaces single character non-ascii characters and URI specific ones by their %xx code. - Wrapper to Python's unquote while remaining compatible with both + Wrapper to Python's `quote` while remaining compatible with both Python 2 & 3 since the reference to this function changed between versions. @@ -421,13 +407,7 @@ class URLBase(object): if not content: return '' - try: - # Python v3.x - return _quote(content, safe=safe, encoding=encoding, errors=errors) - - except TypeError: - # Python v2.7 - return _quote(content, safe=safe) + return _quote(content, safe=safe, encoding=encoding, errors=errors) @staticmethod def pprint(content, privacy=True, mode=PrivacyMode.Outer, @@ -456,7 +436,7 @@ class URLBase(object): # Return 4 Asterisks return '****' - if not isinstance(content, six.string_types) or not content: + if not isinstance(content, str) or not content: # Nothing more to do return '' @@ -471,7 +451,7 @@ class URLBase(object): def urlencode(query, doseq=False, safe='', encoding=None, errors=None): """Convert a mapping object or a sequence of two-element tuples - Wrapper to Python's unquote while remaining compatible with both + Wrapper to Python's `urlencode` while remaining compatible with both Python 2 & 3 since the reference to this function changed between versions. @@ -575,11 +555,6 @@ class URLBase(object): # Nothing further to do return [] - except AttributeError: - # This exception ONLY gets thrown under Python v2.7 if an - # object() is passed in place of the content - return [] - content = parse_phone_no(content) return content @@ -714,13 +689,13 @@ class URLBase(object): for key in ('protocol', 'secure_protocol'): schema = getattr(self, key, None) - if isinstance(schema, six.string_types): + if isinstance(schema, str): schemas.add(schema) elif isinstance(schema, (set, list, tuple)): # Support iterables list types for s in schema: - if isinstance(s, six.string_types): + if isinstance(s, str): schemas.add(s) return schemas diff --git a/apprise/attachment/AttachBase.py b/apprise/attachment/AttachBase.py index aa7174fc..16f6c642 100644 --- a/apprise/attachment/AttachBase.py +++ b/apprise/attachment/AttachBase.py @@ -367,14 +367,7 @@ class AttachBase(URLBase): def __bool__(self): """ - Allows the Apprise object to be wrapped in an Python 3.x based 'if - statement'. True is returned if our content was downloaded correctly. - """ - return True if self.path else False - - def __nonzero__(self): - """ - Allows the Apprise object to be wrapped in an Python 2.x based 'if - statement'. True is returned if our content was downloaded correctly. + Allows the Apprise object to be wrapped in an based 'if statement'. + True is returned if our content was downloaded correctly. """ return True if self.path else False diff --git a/apprise/attachment/AttachBase.pyi b/apprise/attachment/AttachBase.pyi index 9b8eb02a..66b7179d 100644 --- a/apprise/attachment/AttachBase.pyi +++ b/apprise/attachment/AttachBase.pyi @@ -34,4 +34,3 @@ class AttachBase: ) -> Dict[str, Any]: ... def __len__(self) -> int: ... def __bool__(self) -> bool: ... - def __nonzero__(self) -> bool: ... \ No newline at end of file diff --git a/apprise/attachment/AttachHTTP.py b/apprise/attachment/AttachHTTP.py index 1d915ad3..da1d698e 100644 --- a/apprise/attachment/AttachHTTP.py +++ b/apprise/attachment/AttachHTTP.py @@ -25,7 +25,6 @@ import re import os -import six import requests from tempfile import NamedTemporaryFile from .AttachBase import AttachBase @@ -67,7 +66,7 @@ class AttachHTTP(AttachBase): self.schema = 'https' if self.secure else 'http' self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '/' self.headers = {} diff --git a/apprise/attachment/__init__.py b/apprise/attachment/__init__.py index 81eb406c..7f83769a 100644 --- a/apprise/attachment/__init__.py +++ b/apprise/attachment/__init__.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import re from os import listdir @@ -88,7 +87,7 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.attachment'): # Load protocol(s) if defined proto = getattr(plugin, 'protocol', None) - if isinstance(proto, six.string_types): + if isinstance(proto, str): if proto not in ATTACHMENT_SCHEMA_MAP: ATTACHMENT_SCHEMA_MAP[proto] = plugin @@ -100,7 +99,7 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.attachment'): # Load secure protocol(s) if defined protos = getattr(plugin, 'secure_protocol', None) - if isinstance(protos, six.string_types): + if isinstance(protos, str): if protos not in ATTACHMENT_SCHEMA_MAP: ATTACHMENT_SCHEMA_MAP[protos] = plugin diff --git a/apprise/cli.py b/apprise/cli.py index 9bb6262b..0e60e5cd 100644 --- a/apprise/cli.py +++ b/apprise/cli.py @@ -26,7 +26,6 @@ import click import logging import platform -import six import sys import os import re @@ -273,10 +272,10 @@ def main(body, title, config, attach, urls, notification_type, theme, tag, # Set the theme theme=theme, - # Async mode is only used for Python v3+ and allows a user to send - # all of their notifications asyncronously. This was made an option - # incase there are problems in the future where it's better that - # everything run sequentially/syncronously instead. + # Async mode allows a user to send all of their notifications + # asynchronously. This was made an option incase there are problems + # in the future where it is better that everything runs sequentially/ + # synchronously instead. async_mode=disable_async is not True, # Load our plugins @@ -296,11 +295,11 @@ def main(body, title, config, attach, urls, notification_type, theme, tag, for entry in plugins: protocols = [] if not entry['protocols'] else \ [p for p in entry['protocols'] - if isinstance(p, six.string_types)] + if isinstance(p, str)] protocols.extend( [] if not entry['secure_protocols'] else [p for p in entry['secure_protocols'] - if isinstance(p, six.string_types)]) + if isinstance(p, str)]) if len(protocols) == 1: # Simplify view by swapping {schema} with the single diff --git a/apprise/common.py b/apprise/common.py index 45b4ec7b..958bd22e 100644 --- a/apprise/common.py +++ b/apprise/common.py @@ -69,7 +69,7 @@ CONFIG_SCHEMA_MAP = {} ATTACHMENT_SCHEMA_MAP = {} -class NotifyType(object): +class NotifyType: """ A simple mapping of notification types most commonly used with all types of logging and notification services. @@ -88,7 +88,7 @@ NOTIFY_TYPES = ( ) -class NotifyImageSize(object): +class NotifyImageSize: """ A list of pre-defined image sizes to make it easier to work with defined plugins. @@ -107,7 +107,7 @@ NOTIFY_IMAGE_SIZES = ( ) -class NotifyFormat(object): +class NotifyFormat: """ A list of pre-defined text message formats that can be passed via the apprise library. @@ -124,7 +124,7 @@ NOTIFY_FORMATS = ( ) -class OverflowMode(object): +class OverflowMode: """ A list of pre-defined modes of how to handle the text when it exceeds the defined maximum message size. @@ -152,7 +152,7 @@ OVERFLOW_MODES = ( ) -class ConfigFormat(object): +class ConfigFormat: """ A list of pre-defined config formats that can be passed via the apprise library. @@ -175,7 +175,7 @@ CONFIG_FORMATS = ( ) -class ContentIncludeMode(object): +class ContentIncludeMode: """ The different Content inclusion modes. All content based plugins will have one of these associated with it. @@ -200,7 +200,7 @@ CONTENT_INCLUDE_MODES = ( ) -class ContentLocation(object): +class ContentLocation: """ This is primarily used for handling file attachments. The idea is to track the source of the attachment itself. We don't want diff --git a/apprise/config/ConfigBase.py b/apprise/config/ConfigBase.py index 840d4835..d504a98d 100644 --- a/apprise/config/ConfigBase.py +++ b/apprise/config/ConfigBase.py @@ -25,7 +25,6 @@ import os import re -import six import yaml import time @@ -135,7 +134,7 @@ class ConfigBase(URLBase): self.encoding = kwargs.get('encoding') if 'format' in kwargs \ - and isinstance(kwargs['format'], six.string_types): + and isinstance(kwargs['format'], str): # Store the enforced config format self.config_format = kwargs.get('format').lower() @@ -180,7 +179,7 @@ class ConfigBase(URLBase): # config plugin to load the data source and return unparsed content # None is returned if there was an error or simply no data content = self.read(**kwargs) - if not isinstance(content, six.string_types): + if not isinstance(content, str): # Set the time our content was cached at self._cached_time = time.time() @@ -704,7 +703,7 @@ class ConfigBase(URLBase): if not (hasattr(asset, k) and isinstance(getattr(asset, k), - (bool, six.string_types))): + (bool, str))): # We can't set a function or non-string set value ConfigBase.logger.warning( @@ -715,7 +714,7 @@ class ConfigBase(URLBase): # Convert to an empty string v = '' - if (isinstance(v, (bool, six.string_types)) + if (isinstance(v, (bool, str)) and isinstance(getattr(asset, k), bool)): # If the object in the Asset is a boolean, then @@ -723,7 +722,7 @@ class ConfigBase(URLBase): # match that. setattr(asset, k, parse_bool(v)) - elif isinstance(v, six.string_types): + elif isinstance(v, str): # Set our asset object with the new value setattr(asset, k, v.strip()) @@ -738,7 +737,7 @@ class ConfigBase(URLBase): global_tags = set() tags = result.get('tag', None) - if tags and isinstance(tags, (list, tuple, six.string_types)): + if tags and isinstance(tags, (list, tuple, str)): # Store any preset tags global_tags = set(parse_list(tags)) @@ -746,7 +745,7 @@ class ConfigBase(URLBase): # include root directive # includes = result.get('include', None) - if isinstance(includes, six.string_types): + if isinstance(includes, str): # Support a single inline string or multiple ones separated by a # comma and/or space includes = parse_urls(includes) @@ -758,7 +757,7 @@ class ConfigBase(URLBase): # Iterate over each config URL for no, url in enumerate(includes): - if isinstance(url, six.string_types): + if isinstance(url, str): # Support a single inline string or multiple ones separated by # a comma and/or space configs.extend(parse_urls(url)) @@ -786,7 +785,7 @@ class ConfigBase(URLBase): loggable_url = url if not asset.secure_logging \ else cwe312_url(url) - if isinstance(url, six.string_types): + if isinstance(url, str): # We're just a simple URL string... schema = GET_SCHEMA_RE.match(url) if schema is None: @@ -817,10 +816,7 @@ class ConfigBase(URLBase): # can at least tell the end user what entries were ignored # due to errors - if six.PY2: - it = url.iteritems() - else: # six.PY3 - it = iter(url.items()) + it = iter(url.items()) # Track the URL to-load _url = None @@ -870,10 +866,7 @@ class ConfigBase(URLBase): # We are a url string with additional unescaped options if isinstance(entries, dict): - if six.PY2: - _url, tokens = next(url.iteritems()) - else: # six.PY3 - _url, tokens = next(iter(url.items())) + _url, tokens = next(iter(url.items())) # Tags you just can't over-ride if 'schema' in entries: @@ -1114,7 +1107,7 @@ class ConfigBase(URLBase): r'^(choice:)?string', meta.get('type'), re.IGNORECASE) \ - and not isinstance(value, six.string_types): + and not isinstance(value, str): # Ensure our format is as expected value = str(value) @@ -1167,19 +1160,8 @@ class ConfigBase(URLBase): def __bool__(self): """ - Allows the Apprise object to be wrapped in an Python 3.x based 'if - statement'. True is returned if our content was downloaded correctly. - """ - if not isinstance(self._cached_servers, list): - # Generate ourselves a list of content we can pull from - self.servers() - - return True if self._cached_servers else False - - def __nonzero__(self): - """ - Allows the Apprise object to be wrapped in an Python 2.x based 'if - statement'. True is returned if our content was downloaded correctly. + Allows the Apprise object to be wrapped in an 'if statement'. + True is returned if our content was downloaded correctly. """ if not isinstance(self._cached_servers, list): # Generate ourselves a list of content we can pull from diff --git a/apprise/config/ConfigFile.py b/apprise/config/ConfigFile.py index 6fd1ecb2..10f0a463 100644 --- a/apprise/config/ConfigFile.py +++ b/apprise/config/ConfigFile.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import io import os from .ConfigBase import ConfigBase from ..common import ConfigFormat @@ -119,9 +118,7 @@ class ConfigFile(ConfigBase): self.throttle() try: - # Python 3 just supports open(), however to remain compatible with - # Python 2, we use the io module - with io.open(self.path, "rt", encoding=self.encoding) as f: + with open(self.path, "rt", encoding=self.encoding) as f: # Store our content for parsing response = f.read() diff --git a/apprise/config/ConfigHTTP.py b/apprise/config/ConfigHTTP.py index 88352733..795c6fac 100644 --- a/apprise/config/ConfigHTTP.py +++ b/apprise/config/ConfigHTTP.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import requests from .ConfigBase import ConfigBase from ..common import ConfigFormat @@ -81,7 +80,7 @@ class ConfigHTTP(ConfigBase): self.schema = 'https' if self.secure else 'http' self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '/' self.headers = {} diff --git a/apprise/config/__init__.py b/apprise/config/__init__.py index 6b1781b2..78311890 100644 --- a/apprise/config/__init__.py +++ b/apprise/config/__init__.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six from os import listdir from os.path import dirname from os.path import abspath @@ -87,27 +86,7 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.config'): globals()[plugin_name] = plugin fn = getattr(plugin, 'schemas', None) - try: - schemas = set([]) if not callable(fn) else fn(plugin) - - except TypeError: - # Python v2.x support where functions associated with classes - # were considered bound to them and could not be called prior - # to the classes initialization. This code can be dropped - # once Python v2.x support is dropped. The below code introduces - # replication as it already exists and is tested in - # URLBase.schemas() - schemas = set([]) - for key in ('protocol', 'secure_protocol'): - schema = getattr(plugin, key, None) - if isinstance(schema, six.string_types): - schemas.add(schema) - - elif isinstance(schema, (set, list, tuple)): - # Support iterables list types - for s in schema: - if isinstance(s, six.string_types): - schemas.add(s) + schemas = set([]) if not callable(fn) else fn(plugin) # map our schema to our plugin for schema in schemas: diff --git a/apprise/conversion.py b/apprise/conversion.py index 1f289135..3b692aa6 100644 --- a/apprise/conversion.py +++ b/apprise/conversion.py @@ -23,18 +23,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - import re -import six from markdown import markdown from .common import NotifyFormat from .URLBase import URLBase -if six.PY2: - from HTMLParser import HTMLParser - -else: - from html.parser import HTMLParser +from html.parser import HTMLParser def convert_between(from_format, to_format, content): @@ -80,10 +74,6 @@ def html_to_text(content): """ parser = HTMLConverter() - if six.PY2: - # Python 2.7 requires an additional parsing to un-escape characters - content = parser.unescape(content) - parser.feed(content) parser.close() return parser.converted @@ -125,20 +115,6 @@ class HTMLConverter(HTMLParser, object): string = ''.join(self._finalize(self._result)) self.converted = string.strip() - if six.PY2: - # See https://stackoverflow.com/questions/10993612/\ - # how-to-remove-xa0-from-string-in-python - # - # This is required since the unescape() nbsp; with \xa0 when - # using Python 2.7 - try: - self.converted = self.converted.replace(u'\xa0', u' ') - - except UnicodeDecodeError: - # Python v2.7 isn't the greatest for handling unicode - self.converted = \ - self.converted.decode('utf-8').replace(u'\xa0', u' ') - def _finalize(self, result): """ Combines and strips consecutive strings, then converts consecutive diff --git a/apprise/decorators/CustomNotifyPlugin.py b/apprise/decorators/CustomNotifyPlugin.py index 04f8c168..39fb51a9 100644 --- a/apprise/decorators/CustomNotifyPlugin.py +++ b/apprise/decorators/CustomNotifyPlugin.py @@ -22,7 +22,6 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six from ..plugins.NotifyBase import NotifyBase from ..utils import URL_DETAILS_RE from ..utils import parse_url @@ -73,7 +72,7 @@ class CustomNotifyPlugin(NotifyBase): parsed from the provided URL into our supported matrix structure. """ - if not isinstance(url, six.string_types): + if not isinstance(url, str): msg = 'An invalid custom notify url/schema ({}) provided in ' \ 'function {}.'.format(url, send_func.__name__) logger.warning(msg) @@ -112,7 +111,7 @@ class CustomNotifyPlugin(NotifyBase): class CustomNotifyPluginWrapper(CustomNotifyPlugin): # Our Service Name - service_name = name if isinstance(name, six.string_types) \ + service_name = name if isinstance(name, str) \ and name else 'Custom - {}'.format(plugin_name) # Store our matched schema diff --git a/apprise/logger.py b/apprise/logger.py index 08217812..4510e1b6 100644 --- a/apprise/logger.py +++ b/apprise/logger.py @@ -66,7 +66,7 @@ logging.Logger.deprecate = deprecate logger = logging.getLogger(LOGGER_NAME) -class LogCapture(object): +class LogCapture: """ A class used to allow one to instantiate loggers that write to memory for temporary purposes. e.g.: diff --git a/apprise/plugins/NotifyAppriseAPI.py b/apprise/plugins/NotifyAppriseAPI.py index 4dbabf7a..10d52d5b 100644 --- a/apprise/plugins/NotifyAppriseAPI.py +++ b/apprise/plugins/NotifyAppriseAPI.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import requests from json import dumps @@ -137,7 +136,7 @@ class NotifyAppriseAPI(NotifyBase): super(NotifyAppriseAPI, self).__init__(**kwargs) self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '/' self.token = validate_regex( diff --git a/apprise/plugins/NotifyBark.py b/apprise/plugins/NotifyBark.py index d7b178d6..d6283c02 100644 --- a/apprise/plugins/NotifyBark.py +++ b/apprise/plugins/NotifyBark.py @@ -25,7 +25,6 @@ # # API: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#python # -import six import requests import json @@ -76,7 +75,7 @@ BARK_SOUNDS = ( # Supported Level Entries -class NotifyBarkLevel(object): +class NotifyBarkLevel: """ Defines the Bark Level options """ @@ -217,10 +216,10 @@ class NotifyBark(NotifyBase): # Assign our category self.category = \ - category if isinstance(category, six.string_types) else None + category if isinstance(category, str) else None # Assign our group - self.group = group if isinstance(group, six.string_types) else None + self.group = group if isinstance(group, str) else None # Initialize device list self.targets = parse_list(targets) diff --git a/apprise/plugins/NotifyBase.py b/apprise/plugins/NotifyBase.py index ca5a51a4..4bb93779 100644 --- a/apprise/plugins/NotifyBase.py +++ b/apprise/plugins/NotifyBase.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six from ..URLBase import URLBase from ..common import NotifyType @@ -37,14 +36,9 @@ from ..AppriseLocale import gettext_lazy as _ from ..AppriseAttachment import AppriseAttachment -if six.PY3: - # Wrap our base with the asyncio wrapper - from ..py3compat.asyncio import AsyncNotifyBase - BASE_OBJECT = AsyncNotifyBase - -else: - # Python v2.7 (backwards compatibility) - BASE_OBJECT = URLBase +# Wrap our base with the asyncio wrapper +from ..py3compat.asyncio import AsyncNotifyBase +BASE_OBJECT = AsyncNotifyBase class NotifyBase(BASE_OBJECT): diff --git a/apprise/plugins/NotifyBoxcar.py b/apprise/plugins/NotifyBoxcar.py index 1d04c31f..b40b71cd 100644 --- a/apprise/plugins/NotifyBoxcar.py +++ b/apprise/plugins/NotifyBoxcar.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import requests import hmac from json import dumps @@ -181,7 +180,7 @@ class NotifyBoxcar(NotifyBase): self.tags.append(DEFAULT_TAG) targets = [] - elif isinstance(targets, six.string_types): + elif isinstance(targets, str): targets = [x for x in filter(bool, TAGS_LIST_DELIM.split( targets, ))] diff --git a/apprise/plugins/NotifyD7Networks.py b/apprise/plugins/NotifyD7Networks.py index 728f119a..b7575d92 100644 --- a/apprise/plugins/NotifyD7Networks.py +++ b/apprise/plugins/NotifyD7Networks.py @@ -30,7 +30,6 @@ # (both user and password) from the API Details section from within your # account profile area: https://d7networks.com/accounts/profile/ -import six import requests import base64 from json import dumps @@ -54,7 +53,7 @@ D7NETWORKS_HTTP_ERROR_MAP = { # Priorities -class D7SMSPriority(object): +class D7SMSPriority: """ D7 Networks SMS Message Priority """ @@ -192,7 +191,7 @@ class NotifyD7Networks(NotifyBase): # Setup our source address (if defined) self.source = None \ - if not isinstance(source, six.string_types) else source.strip() + if not isinstance(source, str) else source.strip() if not (self.user and self.password): msg = 'A D7 Networks user/pass was not provided.' @@ -232,10 +231,10 @@ class NotifyD7Networks(NotifyBase): auth = '{user}:{password}'.format( user=self.user, password=self.password) - if six.PY3: - # Python 3's versio of b64encode() expects a byte array and not - # a string. To accomodate this, we encode the content here - auth = auth.encode('utf-8') + + # Python 3's versio of b64encode() expects a byte array and not + # a string. To accommodate this, we encode the content here + auth = auth.encode('utf-8') # Prepare our headers headers = { diff --git a/apprise/plugins/NotifyDBus.py b/apprise/plugins/NotifyDBus.py index cdae2da3..b568dfe7 100644 --- a/apprise/plugins/NotifyDBus.py +++ b/apprise/plugins/NotifyDBus.py @@ -60,7 +60,7 @@ try: from dbus.mainloop.glib import DBusGMainLoop LOOP_GLIB = DBusGMainLoop() - except ImportError: + except ImportError: # pragma: no cover # No problem pass @@ -109,7 +109,7 @@ MAINLOOP_MAP = { # Urgencies -class DBusUrgency(object): +class DBusUrgency: LOW = 0 NORMAL = 1 HIGH = 2 @@ -161,10 +161,11 @@ class NotifyDBus(NotifyBase): service_url = 'http://www.freedesktop.org/Software/dbus/' # The default protocols - # Python 3 keys() does not return a list object, it's it's own dict_keys() + # Python 3 keys() does not return a list object, it is its own dict_keys() # object if we were to reference, we wouldn't be backwards compatible with # Python v2. So converting the result set back into a list makes us # compatible + # TODO: Review after dropping support for Python 2. protocol = list(MAINLOOP_MAP.keys()) # A URL that takes you to the setup/help of the specific protocol diff --git a/apprise/plugins/NotifyDapnet.py b/apprise/plugins/NotifyDapnet.py index 8fd4a229..27ea65cd 100644 --- a/apprise/plugins/NotifyDapnet.py +++ b/apprise/plugins/NotifyDapnet.py @@ -58,7 +58,7 @@ from ..utils import parse_list from ..utils import parse_bool -class DapnetPriority(object): +class DapnetPriority: NORMAL = 0 EMERGENCY = 1 diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py index b7f3d685..5858f090 100644 --- a/apprise/plugins/NotifyEmail.py +++ b/apprise/plugins/NotifyEmail.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import smtplib from email.mime.text import MIMEText from email.mime.application import MIMEApplication @@ -47,7 +46,7 @@ from ..AppriseLocale import gettext_lazy as _ charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8') -class WebBaseLogin(object): +class WebBaseLogin: """ This class is just used in conjunction of the default emailers to best formulate a login to it using the data detected @@ -60,7 +59,7 @@ class WebBaseLogin(object): # Secure Email Modes -class SecureMailMode(object): +class SecureMailMode: SSL = "ssl" STARTTLS = "starttls" @@ -480,11 +479,11 @@ class NotifyEmail(NotifyBase): # Now detect the SMTP Server self.smtp_host = \ - smtp_host if isinstance(smtp_host, six.string_types) else '' + smtp_host if isinstance(smtp_host, str) else '' # Now detect secure mode self.secure_mode = self.default_secure_mode \ - if not isinstance(secure_mode, six.string_types) \ + if not isinstance(secure_mode, str) \ else secure_mode.lower() if self.secure_mode not in SECURE_MODES: msg = 'The secure mode specified ({}) is invalid.'\ @@ -684,38 +683,21 @@ class NotifyEmail(NotifyBase): # Strip target out of reply_to list if in To reply_to = (self.reply_to - set([to_addr])) - try: - # Format our cc addresses to support the Name field - cc = [formataddr( - (self.names.get(addr, False), addr), charset='utf-8') - for addr in cc] + # Format our cc addresses to support the Name field + cc = [formataddr( + (self.names.get(addr, False), addr), charset='utf-8') + for addr in cc] + + # Format our bcc addresses to support the Name field + bcc = [formataddr( + (self.names.get(addr, False), addr), charset='utf-8') + for addr in bcc] - # Format our bcc addresses to support the Name field - bcc = [formataddr( + if reply_to: + # Format our reply-to addresses to support the Name field + reply_to = [formataddr( (self.names.get(addr, False), addr), charset='utf-8') - for addr in bcc] - - if reply_to: - # Format our reply-to addresses to support the Name field - reply_to = [formataddr( - (self.names.get(addr, False), addr), charset='utf-8') - for addr in reply_to] - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - cc = [formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) for addr in cc] - - # Format our bcc addresses to support the Name field - bcc = [formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) for addr in bcc] - - if reply_to: - # Format our reply-to addresses to support the Name field - reply_to = [formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) - for addr in reply_to] + for addr in reply_to] self.logger.debug( 'Email From: {} <{}>'.format(from_name, self.from_addr)) @@ -781,25 +763,11 @@ class NotifyEmail(NotifyBase): base[k] = Header(v, self._get_charset(v)) base['Subject'] = Header(title, self._get_charset(title)) - try: - base['From'] = formataddr( - (from_name if from_name else False, self.from_addr), - charset='utf-8') - base['To'] = formataddr((to_name, to_addr), charset='utf-8') - - except TypeError: - # Python v2.x Support (no charset keyword) - base['From'] = formataddr( - (from_name if from_name else False, self.from_addr)) - base['To'] = formataddr((to_name, to_addr)) - - try: - base['Message-ID'] = make_msgid(domain=self.smtp_host) - - except TypeError: - # Python v2.x Support (no domain keyword) - base['Message-ID'] = make_msgid() - + base['From'] = formataddr( + (from_name if from_name else False, self.from_addr), + charset='utf-8') + base['To'] = formataddr((to_name, to_addr), charset='utf-8') + base['Message-ID'] = make_msgid(domain=self.smtp_host) base['Date'] = \ datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000") base['X-Application'] = self.app_id diff --git a/apprise/plugins/NotifyEmby.py b/apprise/plugins/NotifyEmby.py index 45b5bcb3..d609149a 100644 --- a/apprise/plugins/NotifyEmby.py +++ b/apprise/plugins/NotifyEmby.py @@ -677,7 +677,7 @@ class NotifyEmby(NotifyBase): def __del__(self): """ - Deconstructor + Destructor """ try: self.logout() @@ -694,20 +694,20 @@ class NotifyEmby(NotifyBase): # - https://bugs.python.org/issue29288 # # A ~similar~ issue can be identified here in the requests - # ticket system as unresolved and has provided work-arounds + # ticket system as unresolved and has provided workarounds # - https://github.com/kennethreitz/requests/issues/3578 pass except ImportError: # pragma: no cover # The actual exception is `ModuleNotFoundError` however ImportError - # grants us backwards compatiblity with versions of Python older + # grants us backwards compatibility with versions of Python older # than v3.6 # Python code that makes early calls to sys.exit() can cause - # the __del__() code to run. However in some newer versions of + # the __del__() code to run. However, in some newer versions of # Python, this causes the `sys` library to no longer be # available. The stack overflow also goes on to suggest that - # it's not wise to use the __del__() as a deconstructor + # it's not wise to use the __del__() as a destructor # which is the case here. # https://stackoverflow.com/questions/67218341/\ @@ -719,6 +719,6 @@ class NotifyEmby(NotifyBase): # /1481488/what-is-the-del-method-and-how-do-i-call-it # At this time it seems clean to try to log out (if we can) - # but not throw any unessisary exceptions (like this one) to + # but not throw any unnecessary exceptions (like this one) to # the end user if we don't have to. pass diff --git a/apprise/plugins/NotifyEnigma2.py b/apprise/plugins/NotifyEnigma2.py index 60897589..f47b6718 100644 --- a/apprise/plugins/NotifyEnigma2.py +++ b/apprise/plugins/NotifyEnigma2.py @@ -31,7 +31,6 @@ # - https://github.com/E2OpenPlugins/e2openplugin-OpenWebif/wiki/\ # OpenWebif-API-documentation#message -import six import requests from json import loads @@ -41,7 +40,7 @@ from ..common import NotifyType from ..AppriseLocale import gettext_lazy as _ -class Enigma2MessageType(object): +class Enigma2MessageType: # Defines the Enigma2 notification types Apprise can map to INFO = 1 WARNING = 2 @@ -169,7 +168,7 @@ class NotifyEnigma2(NotifyBase): self.timeout = self.template_args['timeout']['default'] self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '/' self.headers = {} diff --git a/apprise/plugins/NotifyFCM/__init__.py b/apprise/plugins/NotifyFCM/__init__.py index fc47984f..eb21028e 100644 --- a/apprise/plugins/NotifyFCM/__init__.py +++ b/apprise/plugins/NotifyFCM/__init__.py @@ -45,7 +45,6 @@ # # If you Generate a new private key, it will provide a .json file # You will need this in order to send an apprise messag -import six import requests from json import dumps from ..NotifyBase import NotifyBase @@ -74,7 +73,7 @@ except ImportError: # cryptography is the dependency of the .oauth library # Create a dummy object for init() call to work - class GoogleOAuth(object): + class GoogleOAuth: pass @@ -228,7 +227,7 @@ class NotifyFCM(NotifyBase): else: # Setup our mode self.mode = NotifyFCM.template_tokens['mode']['default'] \ - if not isinstance(mode, six.string_types) else mode.lower() + if not isinstance(mode, str) else mode.lower() if self.mode and self.mode not in FCM_MODES: msg = 'The FCM mode specified ({}) is invalid.'.format(mode) self.logger.warning(msg) diff --git a/apprise/plugins/NotifyFCM/color.py b/apprise/plugins/NotifyFCM/color.py index a2fcfd66..1f096ab0 100644 --- a/apprise/plugins/NotifyFCM/color.py +++ b/apprise/plugins/NotifyFCM/color.py @@ -31,13 +31,12 @@ # https://firebase.google.com/docs/reference/fcm/rest/v1/\ # projects.messages#androidnotification import re -import six from ...utils import parse_bool from ...common import NotifyType from ...AppriseAsset import AppriseAsset -class FCMColorManager(object): +class FCMColorManager: """ A Simple object to accept either a boolean value - True: Use colors provided by Apprise @@ -63,7 +62,7 @@ class FCMColorManager(object): # Prepare our color self.color = color - if isinstance(color, six.string_types): + if isinstance(color, str): self.color = self.__color_rgb.match(color) if self.color: # Store our RGB value as #rrggbb @@ -112,16 +111,8 @@ class FCMColorManager(object): def __bool__(self): """ - Allows this object to be wrapped in an Python 3.x based 'if - statement'. True is returned if a color was loaded + Allows this object to be wrapped in an 'if statement'. + True is returned if a color was loaded """ return True if self.color is True or \ - isinstance(self.color, six.string_types) else False - - def __nonzero__(self): - """ - Allows this object to be wrapped in an Python 2.x based 'if - statement'. True is returned if a color was loaded - """ - return True if self.color is True or \ - isinstance(self.color, six.string_types) else False + isinstance(self.color, str) else False diff --git a/apprise/plugins/NotifyFCM/common.py b/apprise/plugins/NotifyFCM/common.py index 39765ff0..91bc2a0c 100644 --- a/apprise/plugins/NotifyFCM/common.py +++ b/apprise/plugins/NotifyFCM/common.py @@ -22,7 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -class FCMMode(object): +class FCMMode: """ Define the Firebase Cloud Messaging Modes """ diff --git a/apprise/plugins/NotifyFCM/oauth.py b/apprise/plugins/NotifyFCM/oauth.py index 95dcc3c2..a948d2fe 100644 --- a/apprise/plugins/NotifyFCM/oauth.py +++ b/apprise/plugins/NotifyFCM/oauth.py @@ -29,7 +29,6 @@ # 2. Click Generate New Private Key, then confirm by clicking Generate Key. # 3. Securely store the JSON file containing the key. -import io import requests import base64 import json @@ -41,26 +40,13 @@ from cryptography.hazmat.primitives import asymmetric from cryptography.exceptions import UnsupportedAlgorithm from datetime import datetime from datetime import timedelta -from ...logger import logger - -try: - # Python 2.7 - from urllib import urlencode as _urlencode - -except ImportError: - # Python 3.x - from urllib.parse import urlencode as _urlencode +from json.decoder import JSONDecodeError +from urllib.parse import urlencode as _urlencode -try: - # Python 3.x - from json.decoder import JSONDecodeError - -except ImportError: - # Python v2.7 Backwards Compatibility support - JSONDecodeError = ValueError +from ...logger import logger -class GoogleOAuth(object): +class GoogleOAuth: """ A OAuth simplified implimentation to Google's Firebase Cloud Messaging @@ -127,7 +113,7 @@ class GoogleOAuth(object): self.__access_token_expiry = datetime.utcnow() try: - with io.open(path, mode="r", encoding=self.encoding) as fp: + with open(path, mode="r", encoding=self.encoding) as fp: self.content = json.loads(fp.read()) except (OSError, IOError): diff --git a/apprise/plugins/NotifyFCM/priority.py b/apprise/plugins/NotifyFCM/priority.py index 7c8ab1cc..b6652f27 100644 --- a/apprise/plugins/NotifyFCM/priority.py +++ b/apprise/plugins/NotifyFCM/priority.py @@ -33,7 +33,7 @@ from .common import (FCMMode, FCM_MODES) from ...logger import logger -class NotificationPriority(object): +class NotificationPriority: """ Defines the Notification Priorities as described on: https://firebase.google.com/docs/reference/fcm/rest/v1/\ @@ -63,7 +63,7 @@ class NotificationPriority(object): HIGH = 'HIGH' -class FCMPriority(object): +class FCMPriority: """ Defines our accepted priorites """ @@ -87,7 +87,7 @@ FCM_PRIORITIES = ( ) -class FCMPriorityManager(object): +class FCMPriorityManager: """ A Simple object to make it easier to work with FCM set priorities """ @@ -242,14 +242,7 @@ class FCMPriorityManager(object): def __bool__(self): """ - Allows this object to be wrapped in an Python 3.x based 'if - statement'. True is returned if a priority was loaded - """ - return True if self.priority else False - - def __nonzero__(self): - """ - Allows this object to be wrapped in an Python 2.x based 'if - statement'. True is returned if a priority was loaded + Allows this object to be wrapped in an 'if statement'. + True is returned if a priority was loaded """ return True if self.priority else False diff --git a/apprise/plugins/NotifyForm.py b/apprise/plugins/NotifyForm.py index c7de3517..7ea4202c 100644 --- a/apprise/plugins/NotifyForm.py +++ b/apprise/plugins/NotifyForm.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import requests from .NotifyBase import NotifyBase @@ -137,11 +136,11 @@ class NotifyForm(NotifyBase): super(NotifyForm, self).__init__(**kwargs) self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '' self.method = self.template_args['method']['default'] \ - if not isinstance(method, six.string_types) else method.upper() + if not isinstance(method, str) else method.upper() if self.method not in METHODS: msg = 'The method specified ({}) is invalid.'.format(method) diff --git a/apprise/plugins/NotifyGnome.py b/apprise/plugins/NotifyGnome.py index 1a4ec149..b7bffd2c 100644 --- a/apprise/plugins/NotifyGnome.py +++ b/apprise/plugins/NotifyGnome.py @@ -60,7 +60,7 @@ except (ImportError, ValueError, AttributeError): # Urgencies -class GnomeUrgency(object): +class GnomeUrgency: LOW = 0 NORMAL = 1 HIGH = 2 diff --git a/apprise/plugins/NotifyGotify.py b/apprise/plugins/NotifyGotify.py index 8941e40a..07728923 100644 --- a/apprise/plugins/NotifyGotify.py +++ b/apprise/plugins/NotifyGotify.py @@ -41,7 +41,7 @@ from ..AppriseLocale import gettext_lazy as _ # Priorities -class GotifyPriority(object): +class GotifyPriority: LOW = 0 MODERATE = 3 NORMAL = 5 diff --git a/apprise/plugins/NotifyGrowl.py b/apprise/plugins/NotifyGrowl.py index 76ac266a..921c4852 100644 --- a/apprise/plugins/NotifyGrowl.py +++ b/apprise/plugins/NotifyGrowl.py @@ -46,7 +46,7 @@ except ImportError: # Priorities -class GrowlPriority(object): +class GrowlPriority: LOW = -2 MODERATE = -1 NORMAL = 0 diff --git a/apprise/plugins/NotifyJSON.py b/apprise/plugins/NotifyJSON.py index 8ec7df5b..c9ce1edc 100644 --- a/apprise/plugins/NotifyJSON.py +++ b/apprise/plugins/NotifyJSON.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import requests import base64 from json import dumps @@ -139,11 +138,11 @@ class NotifyJSON(NotifyBase): super(NotifyJSON, self).__init__(**kwargs) self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '' self.method = self.template_args['method']['default'] \ - if not isinstance(method, six.string_types) else method.upper() + if not isinstance(method, str) else method.upper() if self.method not in METHODS: msg = 'The method specified ({}) is invalid.'.format(method) diff --git a/apprise/plugins/NotifyJoin.py b/apprise/plugins/NotifyJoin.py index 87731424..9ac2f244 100644 --- a/apprise/plugins/NotifyJoin.py +++ b/apprise/plugins/NotifyJoin.py @@ -63,7 +63,7 @@ JOIN_IMAGE_XY = NotifyImageSize.XY_72 # Priorities -class JoinPriority(object): +class JoinPriority: LOW = -2 MODERATE = -1 NORMAL = 0 diff --git a/apprise/plugins/NotifyLametric.py b/apprise/plugins/NotifyLametric.py index 3c616d04..1c4eaa04 100644 --- a/apprise/plugins/NotifyLametric.py +++ b/apprise/plugins/NotifyLametric.py @@ -85,7 +85,6 @@ import re -import six import requests from json import dumps from .NotifyBase import NotifyBase @@ -104,7 +103,7 @@ LAMETRIC_APP_ID_DETECTOR_RE = re.compile( LAMETRIC_IS_APP_TOKEN = re.compile(r'^[a-z0-9]{80,}==$', re.I) -class LametricMode(object): +class LametricMode: """ Define Lametric Notification Modes """ @@ -121,7 +120,7 @@ LAMETRIC_MODES = ( ) -class LametricPriority(object): +class LametricPriority: """ Priority of the message """ @@ -158,7 +157,7 @@ LAMETRIC_PRIORITIES = ( ) -class LametricIconType(object): +class LametricIconType: """ Represents the nature of notification. """ @@ -184,7 +183,7 @@ LAMETRIC_ICON_TYPES = ( ) -class LametricSoundCategory(object): +class LametricSoundCategory: """ Define Sound Categories """ @@ -192,7 +191,7 @@ class LametricSoundCategory(object): ALARMS = "alarms" -class LametricSound(object): +class LametricSound: """ There are 2 categories of sounds, to make things simple we just lump them all togther in one class object. @@ -471,7 +470,7 @@ class NotifyLametric(NotifyBase): super(NotifyLametric, self).__init__(**kwargs) self.mode = mode.strip().lower() \ - if isinstance(mode, six.string_types) \ + if isinstance(mode, str) \ else self.template_args['mode']['default'] # Default Cloud Argument @@ -543,7 +542,7 @@ class NotifyLametric(NotifyBase): # assign our icon (if it was defined); we also eliminate # any hashtag (#) entries that might be present self.icon = re.search(r'[#\s]*(?P.+?)\s*$', icon) \ - .group('value') if isinstance(icon, six.string_types) else None + .group('value') if isinstance(icon, str) else None if icon_type not in LAMETRIC_ICON_TYPES: self.icon_type = self.template_args['icon_type']['default'] @@ -557,7 +556,7 @@ class NotifyLametric(NotifyBase): cycles > self.template_args['cycles']['min']) else cycles self.sound = None - if isinstance(sound, six.string_types): + if isinstance(sound, str): # If sound is set, get it's match self.sound = self.sound_lookup(sound.strip().lower()) if self.sound is None: diff --git a/apprise/plugins/NotifyMQTT.py b/apprise/plugins/NotifyMQTT.py index 377107e6..9ea1e44b 100644 --- a/apprise/plugins/NotifyMQTT.py +++ b/apprise/plugins/NotifyMQTT.py @@ -32,7 +32,6 @@ # /blob/master/src/paho/mqtt/client.py import ssl import re -import six from time import sleep from datetime import datetime from os.path import isfile @@ -46,11 +45,6 @@ from ..AppriseLocale import gettext_lazy as _ # Default our global support flag NOTIFY_MQTT_SUPPORT_ENABLED = False -if six.PY2: - # handle Python v2.7 suport - class ConnectionError(Exception): - pass - try: # 3rd party modules import paho.mqtt.client as mqtt diff --git a/apprise/plugins/NotifyMSG91.py b/apprise/plugins/NotifyMSG91.py index f32ad818..acbfa8d4 100644 --- a/apprise/plugins/NotifyMSG91.py +++ b/apprise/plugins/NotifyMSG91.py @@ -41,7 +41,7 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ -class MSG91Route(object): +class MSG91Route: """ Transactional SMS Routes route=1 for promotional, route=4 for transactional SMS. @@ -57,7 +57,7 @@ MSG91_ROUTES = ( ) -class MSG91Country(object): +class MSG91Country: """ Optional value that can be specified on the MSG91 api """ diff --git a/apprise/plugins/NotifyMSTeams.py b/apprise/plugins/NotifyMSTeams.py index e6924712..65631037 100644 --- a/apprise/plugins/NotifyMSTeams.py +++ b/apprise/plugins/NotifyMSTeams.py @@ -76,6 +76,7 @@ import re import requests import json +from json.decoder import JSONDecodeError from .NotifyBase import NotifyBase from ..common import NotifyImageSize @@ -88,13 +89,6 @@ from ..utils import TemplateType from ..AppriseAttachment import AppriseAttachment from ..AppriseLocale import gettext_lazy as _ -try: - from json.decoder import JSONDecodeError - -except ImportError: - # Python v2.7 Backwards Compatibility support - JSONDecodeError = ValueError - class NotifyMSTeams(NotifyBase): """ diff --git a/apprise/plugins/NotifyMailgun.py b/apprise/plugins/NotifyMailgun.py index 49298562..855d65e1 100644 --- a/apprise/plugins/NotifyMailgun.py +++ b/apprise/plugins/NotifyMailgun.py @@ -74,7 +74,7 @@ MAILGUN_HTTP_ERROR_MAP = { # Priorities -class MailgunRegion(object): +class MailgunRegion: US = 'us' EU = 'eu' @@ -383,17 +383,9 @@ class NotifyMailgun(NotifyBase): return False - try: - reply_to = formataddr( - (self.from_name if self.from_name else False, - self.from_addr), charset='utf-8') - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - reply_to = formataddr( - (self.from_name if self.from_name else False, - self.from_addr)) + reply_to = formataddr( + (self.from_name if self.from_name else False, + self.from_addr), charset='utf-8') # Prepare our payload payload = { @@ -461,33 +453,17 @@ class NotifyMailgun(NotifyBase): # Strip target out of bcc list if in To bcc = (bcc - set([to_addr[1]])) - try: - # Prepare our to - to.append(formataddr(to_addr, charset='utf-8')) - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - - # Prepare our to - to.append(formataddr(to_addr)) + # Prepare our `to` + to.append(formataddr(to_addr, charset='utf-8')) # Prepare our To payload['to'] = ','.join(to) if cc: - try: - # Format our cc addresses to support the Name field - payload['cc'] = ','.join([formataddr( - (self.names.get(addr, False), addr), charset='utf-8') - for addr in cc]) - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - payload['cc'] = ','.join([formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) - for addr in cc]) + # Format our cc addresses to support the Name field + payload['cc'] = ','.join([formataddr( + (self.names.get(addr, False), addr), charset='utf-8') + for addr in cc]) # Format our bcc addresses to support the Name field if bcc: diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py index c739e51e..f62b16e0 100644 --- a/apprise/plugins/NotifyMatrix.py +++ b/apprise/plugins/NotifyMatrix.py @@ -28,7 +28,6 @@ # - https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst # import re -import six import requests from markdown import markdown from json import dumps @@ -67,7 +66,7 @@ IS_ROOM_ID = re.compile( r'(?P[a-z0-9.-]+))?\s*$', re.I) -class MatrixMessageType(object): +class MatrixMessageType: """ The Matrix Message types """ @@ -82,7 +81,7 @@ MATRIX_MESSAGE_TYPES = ( ) -class MatrixWebhookMode(object): +class MatrixWebhookMode: # Webhook Mode is disabled DISABLED = "off" @@ -263,7 +262,7 @@ class NotifyMatrix(NotifyBase): # Setup our mode self.mode = self.template_args['mode']['default'] \ - if not isinstance(mode, six.string_types) else mode.lower() + if not isinstance(mode, str) else mode.lower() if self.mode and self.mode not in MATRIX_WEBHOOK_MODES: msg = 'The mode specified ({}) is invalid.'.format(mode) self.logger.warning(msg) @@ -271,7 +270,7 @@ class NotifyMatrix(NotifyBase): # Setup our message type self.msgtype = self.template_args['msgtype']['default'] \ - if not isinstance(msgtype, six.string_types) else msgtype.lower() + if not isinstance(msgtype, str) else msgtype.lower() if self.msgtype and self.msgtype not in MATRIX_MESSAGE_TYPES: msg = 'The msgtype specified ({}) is invalid.'.format(msgtype) self.logger.warning(msg) @@ -411,7 +410,7 @@ class NotifyMatrix(NotifyBase): """ if not hasattr(self, '_re_slack_formatting_rules'): - # Prepare some one-time slack formating variables + # Prepare some one-time slack formatting variables self._re_slack_formatting_map = { # New lines must become the string version @@ -762,7 +761,7 @@ class NotifyMatrix(NotifyBase): # We can't join a room if we're not logged in return None - if not isinstance(room, six.string_types): + if not isinstance(room, str): # Not a supported string return None @@ -850,7 +849,7 @@ class NotifyMatrix(NotifyBase): # We can't create a room if we're not logged in return None - if not isinstance(room, six.string_types): + if not isinstance(room, str): # Not a supported string return None @@ -930,7 +929,7 @@ class NotifyMatrix(NotifyBase): # We can't get a room id if we're not logged in return None - if not isinstance(room, six.string_types): + if not isinstance(room, str): # Not a supported string return None @@ -1109,20 +1108,20 @@ class NotifyMatrix(NotifyBase): # - https://bugs.python.org/issue29288 # # A ~similar~ issue can be identified here in the requests - # ticket system as unresolved and has provided work-arounds + # ticket system as unresolved and has provided workarounds # - https://github.com/kennethreitz/requests/issues/3578 pass except ImportError: # pragma: no cover # The actual exception is `ModuleNotFoundError` however ImportError - # grants us backwards compatiblity with versions of Python older + # grants us backwards compatibility with versions of Python older # than v3.6 # Python code that makes early calls to sys.exit() can cause - # the __del__() code to run. However in some newer versions of + # the __del__() code to run. However, in some newer versions of # Python, this causes the `sys` library to no longer be # available. The stack overflow also goes on to suggest that - # it's not wise to use the __del__() as a deconstructor + # it's not wise to use the __del__() as a destructor # which is the case here. # https://stackoverflow.com/questions/67218341/\ @@ -1134,7 +1133,7 @@ class NotifyMatrix(NotifyBase): # /1481488/what-is-the-del-method-and-how-do-i-call-it # At this time it seems clean to try to log out (if we can) - # but not throw any unessisary exceptions (like this one) to + # but not throw any unnecessary exceptions (like this one) to # the end user if we don't have to. pass diff --git a/apprise/plugins/NotifyMattermost.py b/apprise/plugins/NotifyMattermost.py index 842ceef9..9c776b51 100644 --- a/apprise/plugins/NotifyMattermost.py +++ b/apprise/plugins/NotifyMattermost.py @@ -33,7 +33,6 @@ # - swap http with mmost # - drop /hooks/ reference -import six import requests from json import dumps @@ -156,7 +155,7 @@ class NotifyMattermost(NotifyBase): # our full path self.fullpath = '' if not isinstance( - fullpath, six.string_types) else fullpath.strip() + fullpath, str) else fullpath.strip() # Authorization Token (associated with project) self.token = validate_regex(token) diff --git a/apprise/plugins/NotifyNotica.py b/apprise/plugins/NotifyNotica.py index 1fba8baf..36cd77cb 100644 --- a/apprise/plugins/NotifyNotica.py +++ b/apprise/plugins/NotifyNotica.py @@ -37,7 +37,6 @@ # notica://abc123 # import re -import six import requests from .NotifyBase import NotifyBase @@ -47,7 +46,7 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ -class NoticaMode(object): +class NoticaMode: """ Tracks if we're accessing the notica upstream server or a locally hosted one. @@ -176,7 +175,7 @@ class NotifyNotica(NotifyBase): # prepare our fullpath self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '/' self.headers = {} diff --git a/apprise/plugins/NotifyNotifico.py b/apprise/plugins/NotifyNotifico.py index b0970e19..98766174 100644 --- a/apprise/plugins/NotifyNotifico.py +++ b/apprise/plugins/NotifyNotifico.py @@ -48,8 +48,8 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ -class NotificoFormat(object): - # Resets all formating +class NotificoFormat: + # Resets all formatting Reset = '\x0F' # Formatting @@ -59,7 +59,7 @@ class NotificoFormat(object): BGSwap = '\x16' -class NotificoColor(object): +class NotificoColor: # Resets Color Reset = '\x03' @@ -248,13 +248,13 @@ class NotifyNotifico(NotifyBase): if self.color: # Colors were specified, make sure we capture and correctly # allow them to exist inline in the message - # \g<1> is less ambigious than \1 - body = re.sub(r'\\x03(\d{0,2})', '\x03\g<1>', body) + # \g<1> is less ambiguous than \1 + body = re.sub(r'\\x03(\d{0,2})', r'\\x03\g<1>', body) else: # no colors specified, make sure we strip out any colors found # to make the string read-able - body = re.sub(r'\\x03(\d{1,2}(,[0-9]{1,2})?)?', '', body) + body = re.sub(r'\\x03(\d{1,2}(,[0-9]{1,2})?)?', r'', body) # Prepare our payload payload = { diff --git a/apprise/plugins/NotifyNtfy.py b/apprise/plugins/NotifyNtfy.py index 74a030ab..c7b2475d 100644 --- a/apprise/plugins/NotifyNtfy.py +++ b/apprise/plugins/NotifyNtfy.py @@ -34,7 +34,6 @@ # ntfy://ntfy.local.domain/?priority=max import re import requests -import six from json import loads from json import dumps from os.path import basename @@ -50,7 +49,7 @@ from ..URLBase import PrivacyMode from ..attachment.AttachBase import AttachBase -class NtfyMode(object): +class NtfyMode: """ Define ntfy Notification Modes """ @@ -67,7 +66,7 @@ NTFY_MODES = ( ) -class NtfyPriority(object): +class NtfyPriority: """ Ntfy Priority Definitions """ @@ -247,7 +246,7 @@ class NotifyNtfy(NotifyBase): # Prepare our mode self.mode = mode.strip().lower() \ - if isinstance(mode, six.string_types) \ + if isinstance(mode, str) \ else self.template_args['mode']['default'] if self.mode not in NTFY_MODES: diff --git a/apprise/plugins/NotifyOpsgenie.py b/apprise/plugins/NotifyOpsgenie.py index a78dbb49..47e58099 100644 --- a/apprise/plugins/NotifyOpsgenie.py +++ b/apprise/plugins/NotifyOpsgenie.py @@ -74,7 +74,7 @@ OPSGENIE_CATEGORIES = ( # Regions -class OpsgenieRegion(object): +class OpsgenieRegion: US = 'us' EU = 'eu' @@ -93,7 +93,7 @@ OPSGENIE_REGIONS = ( # Priorities -class OpsgeniePriority(object): +class OpsgeniePriority: LOW = 1 MODERATE = 2 NORMAL = 3 diff --git a/apprise/plugins/NotifyPagerDuty.py b/apprise/plugins/NotifyPagerDuty.py index bea85636..deff363d 100644 --- a/apprise/plugins/NotifyPagerDuty.py +++ b/apprise/plugins/NotifyPagerDuty.py @@ -40,7 +40,7 @@ from ..utils import parse_bool from ..AppriseLocale import gettext_lazy as _ -class PagerDutySeverity(object): +class PagerDutySeverity: """ Defines the Pager Duty Severity Levels """ @@ -63,7 +63,7 @@ PAGERDUTY_SEVERITY_MAP = { # Priorities -class PagerDutyRegion(object): +class PagerDutyRegion: US = 'us' EU = 'eu' diff --git a/apprise/plugins/NotifyParsePlatform.py b/apprise/plugins/NotifyParsePlatform.py index 07cff21d..40582a8d 100644 --- a/apprise/plugins/NotifyParsePlatform.py +++ b/apprise/plugins/NotifyParsePlatform.py @@ -26,7 +26,6 @@ # Official API reference: https://developer.gitter.im/docs/user-resource import re -import six import requests from json import dumps @@ -40,7 +39,7 @@ TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+') # Priorities -class ParsePlatformDevice(object): +class ParsePlatformDevice: # All Devices ALL = 'all' @@ -134,7 +133,7 @@ class NotifyParsePlatform(NotifyBase): super(NotifyParsePlatform, self).__init__(**kwargs) self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '/' # Application ID diff --git a/apprise/plugins/NotifyProwl.py b/apprise/plugins/NotifyProwl.py index 3564d9a6..b41d7566 100644 --- a/apprise/plugins/NotifyProwl.py +++ b/apprise/plugins/NotifyProwl.py @@ -32,7 +32,7 @@ from ..AppriseLocale import gettext_lazy as _ # Priorities -class ProwlPriority(object): +class ProwlPriority: LOW = -2 MODERATE = -1 NORMAL = 0 diff --git a/apprise/plugins/NotifyPushSafer.py b/apprise/plugins/NotifyPushSafer.py index 2d74dc37..3f495702 100644 --- a/apprise/plugins/NotifyPushSafer.py +++ b/apprise/plugins/NotifyPushSafer.py @@ -23,8 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -# We use io because it allows us to test the open() call -import io import base64 import requests from json import loads @@ -36,7 +34,7 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ -class PushSaferSound(object): +class PushSaferSound: """ Defines all of the supported PushSafe sounds """ @@ -248,7 +246,7 @@ PUSHSAFER_SOUND_MAP = { # Priorities -class PushSaferPriority(object): +class PushSaferPriority: LOW = -2 MODERATE = -1 NORMAL = 0 @@ -282,7 +280,7 @@ DEFAULT_PRIORITY = "normal" # Vibrations -class PushSaferVibration(object): +class PushSaferVibration: """ Defines the acceptable vibration settings for notification """ @@ -565,7 +563,7 @@ class NotifyPushSafer(NotifyBase): attachment.url(privacy=True))) try: - with io.open(attachment.path, 'rb') as f: + with open(attachment.path, 'rb') as f: # Output must be in a DataURL format (that's what # PushSafer calls it): attachment = ( diff --git a/apprise/plugins/NotifyPushover.py b/apprise/plugins/NotifyPushover.py index 09fa0229..1dfb5787 100644 --- a/apprise/plugins/NotifyPushover.py +++ b/apprise/plugins/NotifyPushover.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import requests from .NotifyBase import NotifyBase @@ -44,7 +43,7 @@ VALIDATE_DEVICE = re.compile(r'^[a-z0-9_]{1,25}$', re.I) # Priorities -class PushoverPriority(object): +class PushoverPriority: LOW = -2 MODERATE = -1 NORMAL = 0 @@ -53,7 +52,7 @@ class PushoverPriority(object): # Sounds -class PushoverSound(object): +class PushoverSound: PUSHOVER = 'pushover' BIKE = 'bike' BUGLE = 'bugle' @@ -280,7 +279,7 @@ class NotifyPushover(NotifyBase): # Setup our sound self.sound = NotifyPushover.default_pushover_sound \ - if not isinstance(sound, six.string_types) else sound.lower() + if not isinstance(sound, str) else sound.lower() if self.sound and self.sound not in PUSHOVER_SOUNDS: msg = 'The sound specified ({}) is invalid.'.format(sound) self.logger.warning(msg) diff --git a/apprise/plugins/NotifyReddit.py b/apprise/plugins/NotifyReddit.py index 7cd5f688..e5709bf8 100644 --- a/apprise/plugins/NotifyReddit.py +++ b/apprise/plugins/NotifyReddit.py @@ -44,7 +44,6 @@ # - https://www.reddit.com/dev/api/ # - https://www.reddit.com/dev/api/#POST_api_submit # - https://github.com/reddit-archive/reddit/wiki/API -import six import requests from json import loads from datetime import timedelta @@ -66,7 +65,7 @@ REDDIT_HTTP_ERROR_MAP = { } -class RedditMessageKind(object): +class RedditMessageKind: """ Define the kinds of messages supported """ @@ -271,7 +270,7 @@ class NotifyReddit(NotifyBase): self.__access_token_expiry = datetime.utcnow() self.kind = kind.strip().lower() \ - if isinstance(kind, six.string_types) \ + if isinstance(kind, str) \ else self.template_args['kind']['default'] if self.kind not in REDDIT_MESSAGE_KINDS: diff --git a/apprise/plugins/NotifyRocketChat.py b/apprise/plugins/NotifyRocketChat.py index 9131ceef..b8337ff5 100644 --- a/apprise/plugins/NotifyRocketChat.py +++ b/apprise/plugins/NotifyRocketChat.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import requests from json import loads from json import dumps @@ -54,7 +53,7 @@ RC_HTTP_ERROR_MAP = { LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+') -class RocketChatAuthMode(object): +class RocketChatAuthMode: """ The Chat Authentication mode is detected """ @@ -218,7 +217,7 @@ class NotifyRocketChat(NotifyBase): # Authentication mode self.mode = None \ - if not isinstance(mode, six.string_types) \ + if not isinstance(mode, str) \ else mode.lower() if self.mode and self.mode not in ROCKETCHAT_AUTH_MODES: diff --git a/apprise/plugins/NotifyRyver.py b/apprise/plugins/NotifyRyver.py index b825dd77..e186a84b 100644 --- a/apprise/plugins/NotifyRyver.py +++ b/apprise/plugins/NotifyRyver.py @@ -32,7 +32,6 @@ # These are important <---^----------------------------------------^ # import re -import six import requests from json import dumps @@ -44,7 +43,7 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ -class RyverWebhookMode(object): +class RyverWebhookMode: """ Ryver supports to webhook modes """ @@ -152,7 +151,7 @@ class NotifyRyver(NotifyBase): # Store our webhook mode self.mode = None \ - if not isinstance(mode, six.string_types) else mode.lower() + if not isinstance(mode, str) else mode.lower() if self.mode not in RYVER_WEBHOOK_MODES: msg = 'The Ryver webhook mode specified ({}) is invalid.' \ diff --git a/apprise/plugins/NotifySES.py b/apprise/plugins/NotifySES.py index 462ea5f8..1c14d34a 100644 --- a/apprise/plugins/NotifySES.py +++ b/apprise/plugins/NotifySES.py @@ -89,13 +89,7 @@ from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.utils import formataddr from email.header import Header -try: - # Python v3.x - from urllib.parse import quote - -except ImportError: - # Python v2.x - from urllib import quote +from urllib.parse import quote from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode @@ -395,26 +389,15 @@ class NotifySES(NotifyBase): # Strip target out of bcc list if in To bcc = (self.bcc - set([to_addr])) - try: - # Format our cc addresses to support the Name field - cc = [formataddr( - (self.names.get(addr, False), addr), charset='utf-8') - for addr in cc] - - # Format our bcc addresses to support the Name field - bcc = [formataddr( - (self.names.get(addr, False), addr), charset='utf-8') - for addr in bcc] + # Format our cc addresses to support the Name field + cc = [formataddr( + (self.names.get(addr, False), addr), charset='utf-8') + for addr in cc] - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - cc = [formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) for addr in cc] - - # Format our bcc addresses to support the Name field - bcc = [formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) for addr in bcc] + # Format our bcc addresses to support the Name field + bcc = [formataddr( + (self.names.get(addr, False), addr), charset='utf-8') + for addr in bcc] self.logger.debug('Email From: {} <{}>'.format( quote(reply_to[0], ' '), @@ -436,23 +419,14 @@ class NotifySES(NotifyBase): # Create a Multipart container if there is an attachment base = MIMEMultipart() if attach else content + # TODO: Deduplicate with `NotifyEmail`? base['Subject'] = Header(title, 'utf-8') - try: - base['From'] = formataddr( - (from_name if from_name else False, self.from_addr), - charset='utf-8') - base['To'] = formataddr((to_name, to_addr), charset='utf-8') - if reply_to[1] != self.from_addr: - base['Reply-To'] = formataddr(reply_to, charset='utf-8') - - except TypeError: - # Python v2.x Support (no charset keyword) - base['From'] = formataddr( - (from_name if from_name else False, self.from_addr)) - base['To'] = formataddr((to_name, to_addr)) - if reply_to[1] != self.from_addr: - base['Reply-To'] = formataddr(reply_to) - + base['From'] = formataddr( + (from_name if from_name else False, self.from_addr), + charset='utf-8') + base['To'] = formataddr((to_name, to_addr), charset='utf-8') + if reply_to[1] != self.from_addr: + base['Reply-To'] = formataddr(reply_to, charset='utf-8') base['Cc'] = ','.join(cc) base['Date'] = \ datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000") diff --git a/apprise/plugins/NotifySMSEagle.py b/apprise/plugins/NotifySMSEagle.py index 157d58be..40e56269 100644 --- a/apprise/plugins/NotifySMSEagle.py +++ b/apprise/plugins/NotifySMSEagle.py @@ -47,7 +47,7 @@ CONTACT_REGEX = re.compile( # Priorities -class SMSEaglePriority(object): +class SMSEaglePriority: NORMAL = 0 HIGH = 1 diff --git a/apprise/plugins/NotifySMTP2Go.py b/apprise/plugins/NotifySMTP2Go.py index 341ad8a6..952604a7 100644 --- a/apprise/plugins/NotifySMTP2Go.py +++ b/apprise/plugins/NotifySMTP2Go.py @@ -315,17 +315,9 @@ class NotifySMTP2Go(NotifyBase): self.logger.debug('I/O Exception: %s' % str(e)) return False - try: - sender = formataddr( - (self.from_name if self.from_name else False, - self.from_addr), charset='utf-8') - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - sender = formataddr( - (self.from_name if self.from_name else False, - self.from_addr)) + sender = formataddr( + (self.from_name if self.from_name else False, + self.from_addr), charset='utf-8') # Prepare our payload payload = { @@ -369,33 +361,17 @@ class NotifySMTP2Go(NotifyBase): # Strip target out of bcc list if in To bcc = (bcc - set([to_addr[1]])) - try: - # Prepare our to - to.append(formataddr(to_addr, charset='utf-8')) - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - - # Prepare our to - to.append(formataddr(to_addr)) + # Prepare our `to` + to.append(formataddr(to_addr, charset='utf-8')) # Prepare our To payload['to'] = to if cc: - try: - # Format our cc addresses to support the Name field - payload['cc'] = [formataddr( - (self.names.get(addr, False), addr), charset='utf-8') - for addr in cc] - - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - payload['cc'] = [formataddr( # pragma: no branch - (self.names.get(addr, False), addr)) - for addr in cc] + # Format our cc addresses to support the Name field + payload['cc'] = [formataddr( + (self.names.get(addr, False), addr), charset='utf-8') + for addr in cc] # Format our bcc addresses to support the Name field if bcc: diff --git a/apprise/plugins/NotifySinch.py b/apprise/plugins/NotifySinch.py index 61ec452d..f911cdb7 100644 --- a/apprise/plugins/NotifySinch.py +++ b/apprise/plugins/NotifySinch.py @@ -33,7 +33,6 @@ # from). Activated phone numbers can be found on your dashboard here: # - https://dashboard.sinch.com/numbers/your-numbers/numbers # -import six import requests import json @@ -46,7 +45,7 @@ from ..utils import validate_regex from ..AppriseLocale import gettext_lazy as _ -class SinchRegion(object): +class SinchRegion: """ Defines the Sinch Server Regions """ @@ -192,7 +191,7 @@ class NotifySinch(NotifyBase): # Setup our region self.region = self.template_args['region']['default'] \ - if not isinstance(region, six.string_types) else region.lower() + if not isinstance(region, str) else region.lower() if self.region and self.region not in SINCH_REGIONS: msg = 'The region specified ({}) is invalid.'.format(region) self.logger.warning(msg) diff --git a/apprise/plugins/NotifySlack.py b/apprise/plugins/NotifySlack.py index cd861478..3b0c733a 100644 --- a/apprise/plugins/NotifySlack.py +++ b/apprise/plugins/NotifySlack.py @@ -94,7 +94,7 @@ SLACK_HTTP_ERROR_MAP = { CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+') -class SlackMode(object): +class SlackMode: """ Tracks the mode of which we're using Slack """ diff --git a/apprise/plugins/NotifySparkPost.py b/apprise/plugins/NotifySparkPost.py index 78ed9f08..93ce84d5 100644 --- a/apprise/plugins/NotifySparkPost.py +++ b/apprise/plugins/NotifySparkPost.py @@ -80,7 +80,7 @@ SPARKPOST_HTTP_ERROR_MAP = { # Priorities -class SparkPostRegion(object): +class SparkPostRegion: US = 'us' EU = 'eu' @@ -503,14 +503,9 @@ class NotifySparkPost(NotifyBase): # Send in batches if identified to do so batch_size = 1 if not self.batch else self.default_batch_size - try: - reply_to = formataddr((self.from_name if self.from_name else False, - self.from_addr), charset='utf-8') - except TypeError: - # Python v2.x Support (no charset keyword) - # Format our cc addresses to support the Name field - reply_to = formataddr((self.from_name if self.from_name else False, - self.from_addr)) + reply_to = formataddr((self.from_name if self.from_name else False, + self.from_addr), charset='utf-8') + payload = { "options": { # When set to True, an image is included with the email which diff --git a/apprise/plugins/NotifyStreamlabs.py b/apprise/plugins/NotifyStreamlabs.py index 27aea289..e4217151 100644 --- a/apprise/plugins/NotifyStreamlabs.py +++ b/apprise/plugins/NotifyStreamlabs.py @@ -42,7 +42,7 @@ from ..AppriseLocale import gettext_lazy as _ # calls -class StrmlabsCall(object): +class StrmlabsCall: ALERT = 'ALERTS' DONATION = 'DONATIONS' @@ -55,7 +55,7 @@ STRMLABS_CALLS = ( # alerts -class StrmlabsAlert(object): +class StrmlabsAlert: FOLLOW = 'follow' SUBSCRIPTION = 'subscription' DONATION = 'donation' diff --git a/apprise/plugins/NotifySyslog.py b/apprise/plugins/NotifySyslog.py index 4fa9d915..20d20b19 100644 --- a/apprise/plugins/NotifySyslog.py +++ b/apprise/plugins/NotifySyslog.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os -import six import syslog import socket @@ -101,7 +100,7 @@ SYSLOG_FACILITY_RMAP = { } -class SyslogMode(object): +class SyslogMode: # A local query LOCAL = "local" @@ -217,7 +216,7 @@ class NotifySyslog(NotifyBase): self.template_tokens['facility']['default']] self.mode = self.template_args['mode']['default'] \ - if not isinstance(mode, six.string_types) else mode.lower() + if not isinstance(mode, str) else mode.lower() if self.mode not in SYSLOG_MODES: msg = 'The mode specified ({}) is invalid.'.format(mode) diff --git a/apprise/plugins/NotifyTwist.py b/apprise/plugins/NotifyTwist.py index 7f9c7c88..d1120868 100644 --- a/apprise/plugins/NotifyTwist.py +++ b/apprise/plugins/NotifyTwist.py @@ -785,7 +785,7 @@ class NotifyTwist(NotifyBase): def __del__(self): """ - Deconstructor + Destructor """ try: self.logout() @@ -808,14 +808,14 @@ class NotifyTwist(NotifyBase): except ImportError: # pragma: no cover # The actual exception is `ModuleNotFoundError` however ImportError - # grants us backwards compatiblity with versions of Python older + # grants us backwards compatibility with versions of Python older # than v3.6 # Python code that makes early calls to sys.exit() can cause - # the __del__() code to run. However in some newer versions of + # the __del__() code to run. However, in some newer versions of # Python, this causes the `sys` library to no longer be # available. The stack overflow also goes on to suggest that - # it's not wise to use the __del__() as a deconstructor + # it's not wise to use the __del__() as a destructor # which is the case here. # https://stackoverflow.com/questions/67218341/\ @@ -827,6 +827,6 @@ class NotifyTwist(NotifyBase): # /1481488/what-is-the-del-method-and-how-do-i-call-it # At this time it seems clean to try to log out (if we can) - # but not throw any unessisary exceptions (like this one) to + # but not throw any unnecessary exceptions (like this one) to # the end user if we don't have to. pass diff --git a/apprise/plugins/NotifyTwitter.py b/apprise/plugins/NotifyTwitter.py index 8483fad6..f26d30fa 100644 --- a/apprise/plugins/NotifyTwitter.py +++ b/apprise/plugins/NotifyTwitter.py @@ -26,7 +26,6 @@ # See https://developer.twitter.com/en/docs/direct-messages/\ # sending-and-receiving/api-reference/new-event.html import re -import six import requests from copy import deepcopy from datetime import datetime @@ -45,7 +44,7 @@ from ..attachment.AttachBase import AttachBase IS_USER = re.compile(r'^\s*@?(?P[A-Z0-9_]+)$', re.I) -class TwitterMessageMode(object): +class TwitterMessageMode: """ Twitter Message Mode """ @@ -223,7 +222,7 @@ class NotifyTwitter(NotifyBase): # Store our webhook mode self.mode = None \ - if not isinstance(mode, six.string_types) else mode.lower() + if not isinstance(mode, str) else mode.lower() # Set Cache Flag self.cache = cache diff --git a/apprise/plugins/NotifyXML.py b/apprise/plugins/NotifyXML.py index 1e57b27f..9fea49da 100644 --- a/apprise/plugins/NotifyXML.py +++ b/apprise/plugins/NotifyXML.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import requests import base64 @@ -157,11 +156,11 @@ class NotifyXML(NotifyBase): """ self.fullpath = kwargs.get('fullpath') - if not isinstance(self.fullpath, six.string_types): + if not isinstance(self.fullpath, str): self.fullpath = '' self.method = self.template_args['method']['default'] \ - if not isinstance(method, six.string_types) else method.upper() + if not isinstance(method, str) else method.upper() if self.method not in METHODS: msg = 'The method specified ({}) is invalid.'.format(method) diff --git a/apprise/plugins/__init__.py b/apprise/plugins/__init__.py index 91131260..9abc3e06 100644 --- a/apprise/plugins/__init__.py +++ b/apprise/plugins/__init__.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import os -import six import re import copy @@ -120,27 +119,7 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.plugins'): globals()[plugin_name] = plugin fn = getattr(plugin, 'schemas', None) - try: - schemas = set([]) if not callable(fn) else fn(plugin) - - except TypeError: - # Python v2.x support where functions associated with classes - # were considered bound to them and could not be called prior - # to the classes initialization. This code can be dropped - # once Python v2.x support is dropped. The below code introduces - # replication as it already exists and is tested in - # URLBase.schemas() - schemas = set([]) - for key in ('protocol', 'secure_protocol'): - schema = getattr(plugin, key, None) - if isinstance(schema, six.string_types): - schemas.add(schema) - - elif isinstance(schema, (set, list, tuple)): - # Support iterables list types - for s in schema: - if isinstance(s, six.string_types): - schemas.add(s) + schemas = set([]) if not callable(fn) else fn(plugin) # map our schema to our plugin for schema in schemas: @@ -232,7 +211,7 @@ def _sanitize_token(tokens, default_delimiter): if 'regex' in tokens[key]: # Verify that we are a tuple; convert strings to tuples - if isinstance(tokens[key]['regex'], six.string_types): + if isinstance(tokens[key]['regex'], str): # Default tuple setup tokens[key]['regex'] = \ (tokens[key]['regex'], None) @@ -473,7 +452,7 @@ def requirements(plugin): # Get our required packages _req_packages = plugin.requirements.get('packages_required') - if isinstance(_req_packages, six.string_types): + if isinstance(_req_packages, str): # Convert to list _req_packages = [_req_packages] @@ -485,7 +464,7 @@ def requirements(plugin): # Get our recommended packages _opt_packages = plugin.requirements.get('packages_recommended') - if isinstance(_opt_packages, six.string_types): + if isinstance(_opt_packages, str): # Convert to list _opt_packages = [_opt_packages] diff --git a/apprise/py3compat/asyncio.py b/apprise/py3compat/asyncio.py index ddcad010..a5313906 100644 --- a/apprise/py3compat/asyncio.py +++ b/apprise/py3compat/asyncio.py @@ -36,9 +36,7 @@ ASYNCIO_RUN_SUPPORT = \ (sys.version_info.major == 3 and sys.version_info.minor >= 7) -# async reference produces a SyntaxError (E999) in Python v2.7 -# For this reason we turn on the noqa flag -async def notify(coroutines): # noqa: E999 +async def notify(coroutines): """ An async wrapper to the AsyncNotifyBase.async_notify() calls allowing us to call gather() and collect the responses @@ -98,7 +96,7 @@ def tosync(cor, debug=False): return loop.run_until_complete(cor) -async def toasyncwrapvalue(v): # noqa: E999 +async def toasyncwrapvalue(v): """ Create a coroutine that, when run, returns the provided value. """ @@ -106,7 +104,7 @@ async def toasyncwrapvalue(v): # noqa: E999 return v -async def toasyncwrap(fn): # noqa: E999 +async def toasyncwrap(fn): """ Create a coroutine that, when run, executes the provided function. """ @@ -119,7 +117,7 @@ class AsyncNotifyBase(URLBase): asyncio wrapper for the NotifyBase object """ - async def async_notify(self, *args, **kwargs): # noqa: E999 + async def async_notify(self, *args, **kwargs): """ Async Notification Wrapper """ @@ -131,11 +129,11 @@ class AsyncNotifyBase(URLBase): None, partial(self.notify, *args, **kwargs)) except TypeError: - # These our our internally thrown notifications + # These are our internally thrown notifications pass except Exception: - # A catch all so we don't have to abort early + # A catch-all so we don't have to abort early # just because one of our plugins has a bug in it. logger.exception("Notification Exception") diff --git a/apprise/utils.py b/apprise/utils.py index af839cb8..334711de 100644 --- a/apprise/utils.py +++ b/apprise/utils.py @@ -24,7 +24,6 @@ # THE SOFTWARE. import re -import six import sys import json import contextlib @@ -36,63 +35,40 @@ from functools import reduce from . import common from .logger import logger -try: - # Python 2.7 - from urllib import unquote - from urllib import quote - from urlparse import urlparse - from urllib import urlencode as _urlencode +from urllib.parse import unquote +from urllib.parse import quote +from urllib.parse import urlparse +from urllib.parse import urlencode as _urlencode - import imp +import importlib.util - def import_module(path, name): - """ - Load our module based on path - """ - try: - return imp.load_source(name, path) - except Exception as e: - logger.debug( - 'Custom module exception raised from %s (name=%s) %s', - path, name, str(e)) - - return None - -except ImportError: - # Python 3.5+ - from urllib.parse import unquote - from urllib.parse import quote - from urllib.parse import urlparse - from urllib.parse import urlencode as _urlencode - - import importlib.util +def import_module(path, name): + """ + Load our module based on path + """ + # if path.endswith('test_module_detection0/a/hook.py'): + # import pdb + # pdb.set_trace() - def import_module(path, name): - """ - Load our module based on path - """ - # if path.endswith('test_module_detection0/a/hook.py'): - # import pdb - # pdb.set_trace() + spec = importlib.util.spec_from_file_location(name, path) + try: + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module - spec = importlib.util.spec_from_file_location(name, path) - try: - module = importlib.util.module_from_spec(spec) - sys.modules[name] = module + spec.loader.exec_module(module) - spec.loader.exec_module(module) + except Exception as e: + # module isn't loadable + del sys.modules[name] + module = None - except Exception as e: - # module isn't loadable - del sys.modules[name] - module = None + logger.debug( + 'Custom module exception raised from %s (name=%s) %s', + path, name, str(e)) - logger.debug( - 'Custom module exception raised from %s (name=%s) %s', - path, name, str(e)) + return module - return module # Hash of all paths previously scanned so we don't waste effort/overhead doing # it again @@ -226,7 +202,7 @@ UUID4_RE = re.compile( REGEX_VALIDATE_LOOKUP = {} -class TemplateType(object): +class TemplateType: """ Defines the different template types we can perform parsing on """ @@ -690,7 +666,7 @@ def parse_url(url, default_schema='http', verify_host=True, strict_port=False, """ - if not isinstance(url, six.string_types): + if not isinstance(url, str): # Simple error checking return None @@ -862,10 +838,10 @@ def parse_url(url, default_schema='http', verify_host=True, strict_port=False, # Re-assemble cleaned up version of the url result['url'] = '%s://' % result['schema'] - if isinstance(result.get('user'), six.string_types): + if isinstance(result.get('user'), str): result['url'] += result['user'] - if isinstance(result.get('password'), six.string_types): + if isinstance(result.get('password'), str): result['url'] += ':%s@' % result['password'] else: @@ -900,7 +876,7 @@ def parse_bool(arg, default=False): If the content could not be parsed, then the default is returned. """ - if isinstance(arg, six.string_types): + if isinstance(arg, str): # no = no - False # of = short for off - False # 0 = int for False @@ -930,20 +906,15 @@ def parse_bool(arg, default=False): return bool(arg) -def parse_phone_no(*args, **kwargs): +def parse_phone_no(*args, store_unparseable=True, **kwargs): """ Takes a string containing phone numbers separated by comma's and/or spaces and returns a list. """ - # for Python 2.7 support, store_unparsable is not in the url above - # as just parse_emails(*args, store_unparseable=True) since it is - # an invalid syntax. This is the workaround to be backards compatible: - store_unparseable = kwargs.get('store_unparseable', True) - result = [] for arg in args: - if isinstance(arg, six.string_types) and arg: + if isinstance(arg, str) and arg: _result = PHONE_NO_DETECTION_RE.findall(arg) if _result: result += _result @@ -967,20 +938,15 @@ def parse_phone_no(*args, **kwargs): return result -def parse_call_sign(*args, **kwargs): +def parse_call_sign(*args, store_unparseable=True, **kwargs): """ Takes a string containing ham radio call signs separated by comma and/or spacesand returns a list. """ - # for Python 2.7 support, store_unparsable is not in the url above - # as just parse_emails(*args, store_unparseable=True) since it is - # an invalid syntax. This is the workaround to be backards compatible: - store_unparseable = kwargs.get('store_unparseable', True) - result = [] for arg in args: - if isinstance(arg, six.string_types) and arg: + if isinstance(arg, str) and arg: _result = CALL_SIGN_DETECTION_RE.findall(arg) if _result: result += _result @@ -1004,20 +970,15 @@ def parse_call_sign(*args, **kwargs): return result -def parse_emails(*args, **kwargs): +def parse_emails(*args, store_unparseable=True, **kwargs): """ Takes a string containing emails separated by comma's and/or spaces and returns a list. """ - # for Python 2.7 support, store_unparsable is not in the url above - # as just parse_emails(*args, store_unparseable=True) since it is - # an invalid syntax. This is the workaround to be backards compatible: - store_unparseable = kwargs.get('store_unparseable', True) - result = [] for arg in args: - if isinstance(arg, six.string_types) and arg: + if isinstance(arg, str) and arg: _result = EMAIL_DETECTION_RE.findall(arg) if _result: result += _result @@ -1040,20 +1001,15 @@ def parse_emails(*args, **kwargs): return result -def parse_urls(*args, **kwargs): +def parse_urls(*args, store_unparseable=True, **kwargs): """ Takes a string containing URLs separated by comma's and/or spaces and returns a list. """ - # for Python 2.7 support, store_unparsable is not in the url above - # as just parse_urls(*args, store_unparseable=True) since it is - # an invalid syntax. This is the workaround to be backards compatible: - store_unparseable = kwargs.get('store_unparseable', True) - result = [] for arg in args: - if isinstance(arg, six.string_types) and arg: + if isinstance(arg, str) and arg: _result = URL_DETECTION_RE.findall(arg) if _result: result += _result @@ -1140,15 +1096,9 @@ def urlencode(query, doseq=False, safe='', encoding=None, errors=None): """ # Tidy query by eliminating any records set to None _query = {k: v for (k, v) in query.items() if v is not None} - try: - # Python v3.x - return _urlencode( - _query, doseq=doseq, safe=safe, encoding=encoding, - errors=errors) - - except TypeError: - # Python v2.7 - return _urlencode(_query) + return _urlencode( + _query, doseq=doseq, safe=safe, encoding=encoding, + errors=errors) def parse_list(*args): @@ -1174,7 +1124,7 @@ def parse_list(*args): result = [] for arg in args: - if isinstance(arg, six.string_types): + if isinstance(arg, str): result += re.split(STRING_DELIMITERS, arg) elif isinstance(arg, (set, list, tuple)): @@ -1183,9 +1133,10 @@ def parse_list(*args): # # filter() eliminates any empty entries # - # Since Python v3 returns a filter (iterator) where-as Python v2 returned + # Since Python v3 returns a filter (iterator) whereas Python v2 returned # a list, we need to change it into a list object to remain compatible with # both distribution types. + # TODO: Review after dropping support for Python 2. return sorted([x for x in filter(bool, list(set(result)))]) @@ -1211,7 +1162,7 @@ def is_exclusive_match(logic, data, match_all=common.MATCH_ALL_TAG, to all specified logic searches. """ - if isinstance(logic, six.string_types): + if isinstance(logic, str): # Update our logic to support our delimiters logic = set(parse_list(logic)) @@ -1234,7 +1185,7 @@ def is_exclusive_match(logic, data, match_all=common.MATCH_ALL_TAG, # Every entry here will be or'ed with the next for entry in logic: - if not isinstance(entry, (six.string_types, list, tuple, set)): + if not isinstance(entry, (str, list, tuple, set)): # Garbage entry in our logic found return False @@ -1300,7 +1251,7 @@ def validate_regex(value, regex=r'[^\s]+', flags=re.I, strip=True, fmt=None): 'x': re.X, } - if isinstance(flags, six.string_types): + if isinstance(flags, str): # Convert a string of regular expression flags into their # respected integer (expected) Python values and perform # a bit-wise or on each match found: @@ -1355,7 +1306,7 @@ def cwe312_word(word, force=False, advanced=True, threshold=5): reached, then content is considered secret """ - class Variance(object): + class Variance: """ A Simple List of Possible Character Variances """ @@ -1368,7 +1319,7 @@ def cwe312_word(word, force=False, advanced=True, threshold=5): # A Numerical Character (1234... etc) NUMERIC = 'n' - if not (isinstance(word, six.string_types) and word.strip()): + if not (isinstance(word, str) and word.strip()): # not a password if it's not something we even support return word @@ -1594,7 +1545,7 @@ def module_detection(paths, cache=True): module_re = re.compile( r'^(?P[_a-z0-9][a-z0-9._-]+)?(\.py)?$', re.I) - if isinstance(paths, six.string_types): + if isinstance(paths, str): paths = [paths, ] if not paths or not isinstance(paths, (tuple, list)): diff --git a/bin/apprise b/bin/apprise index a20f3e18..913a5bad 100755 --- a/bin/apprise +++ b/bin/apprise @@ -41,20 +41,20 @@ from os.path import dirname # First assume we might be in the ./bin directory sys.path.insert( - 0, join(dirname(dirname(abspath(__file__))))) # noqa + 0, join(dirname(dirname(abspath(__file__))))) # The user might have copied the apprise script back one directory # so support this too.. sys.path.insert( - 0, join(dirname(abspath(__file__)))) # noqa + 0, join(dirname(abspath(__file__)))) # We can also use the current directory we're standing in as a last # resort -sys.path.insert(0, join(getcwd())) # noqa +sys.path.insert(0, join(getcwd())) # Apprise tool now importable -from apprise.cli import main -import logging +from apprise.cli import main # noqa E402 +import logging # noqa E402 if __name__ == "__main__": diff --git a/dev-requirements.txt b/dev-requirements.txt index 6fa025bb..34852f59 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,5 @@ coverage flake8 -mock; python_version=='2.7' pytest pytest-cov tox diff --git a/packaging/README.md b/packaging/README.md index d11ffd61..85a7970d 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -1,22 +1,17 @@ ## Packaging -This directory contains any supporting files to grant usage of apprise in various distributions. +This directory contains any supporting files to grant usage of Apprise in various distributions. ### RPM Based Packages -* [EPEL](https://fedoraproject.org/wiki/EPEL) based distributions are only supported if they are of v7 or higher. This includes: - * Red Hat 7.x (or higher) - * CentOS 7.x (or higher) - * Scientific OS 7.x (or higher) - * Oracle Linux 7.x (or higher) +* [EPEL](https://fedoraproject.org/wiki/EPEL) based distributions are only supported if they are of v8 or higher. This includes: + * Red Hat 8.x (or higher) + * Scientific OS 8.x (or higher) + * Oracle Linux 8.x (or higher) + * Rocky Linux 8.x (or higher) + * Alma Linux 8.x (or higher) + * Fedora 29 (or higher) Provided you are connected to the [EPEL repositories](https://fedoraproject.org/wiki/EPEL), the following will just work for you: ```bash -# python2-apprise: contains all you need to develop with apprise -# apprise: provides the 'apprise' administrative tool -yum install python2-apprise apprise -``` - -**Fedora** packaging is available right out of the box; the following will get you going on any distribution (v29 or higher): -```bash # python3-apprise: contains all you need to develop with apprise # apprise: provides the 'apprise' administrative tool dnf install python3-apprise apprise diff --git a/packaging/redhat/apprise-rhel7-support.patch b/packaging/redhat/apprise-rhel7-support.patch deleted file mode 100644 index 464346ce..00000000 --- a/packaging/redhat/apprise-rhel7-support.patch +++ /dev/null @@ -1,48 +0,0 @@ -diff -Naur apprise-1.0.0/test/helpers/rest.py apprise-1.0.0.patched/test/helpers/rest.py ---- apprise-1.0.0/test/helpers/rest.py 2022-07-01 11:37:34.000000000 -0400 -+++ apprise-1.0.0.patched/test/helpers/rest.py 2022-08-06 13:30:29.187325564 -0400 -@@ -54,8 +54,6 @@ - 0, 'requests.RequestException() not handled'), - requests.HTTPError( - 0, 'requests.HTTPError() not handled'), -- requests.ReadTimeout( -- 0, 'requests.ReadTimeout() not handled'), - requests.TooManyRedirects( - 0, 'requests.TooManyRedirects() not handled'), - ) -diff -Naur apprise-1.0.0/test/test_attach_http.py apprise-1.0.0.patched/test/test_attach_http.py ---- apprise-1.0.0/test/test_attach_http.py 2022-07-15 14:52:13.000000000 -0400 -+++ apprise-1.0.0.patched/test/test_attach_http.py 2022-08-06 13:30:29.188325562 -0400 -@@ -51,8 +51,6 @@ - 0, 'requests.RequestException() not handled'), - requests.HTTPError( - 0, 'requests.HTTPError() not handled'), -- requests.ReadTimeout( -- 0, 'requests.ReadTimeout() not handled'), - requests.TooManyRedirects( - 0, 'requests.TooManyRedirects() not handled'), - -diff -Naur apprise-1.0.0/test/test_config_http.py apprise-1.0.0.patched/test/test_config_http.py ---- apprise-1.0.0/test/test_config_http.py 2022-07-15 14:52:13.000000000 -0400 -+++ apprise-1.0.0.patched/test/test_config_http.py 2022-08-06 13:30:29.188325562 -0400 -@@ -46,8 +46,6 @@ - 0, 'requests.RequestException() not handled'), - requests.HTTPError( - 0, 'requests.HTTPError() not handled'), -- requests.ReadTimeout( -- 0, 'requests.ReadTimeout() not handled'), - requests.TooManyRedirects( - 0, 'requests.TooManyRedirects() not handled'), - ) -diff -Naur apprise-1.0.0/test/test_plugin_glib.py apprise-1.0.0.patched/test/test_plugin_glib.py ---- apprise-1.0.0/test/test_plugin_glib.py 2022-07-15 14:52:13.000000000 -0400 -+++ apprise-1.0.0.patched/test/test_plugin_glib.py 2022-08-06 13:30:29.189325559 -0400 -@@ -49,7 +49,7 @@ - - if 'dbus' not in sys.modules: - # Environment doesn't allow for dbus -- pytest.skip("Skipping dbus-python based tests", allow_module_level=True) -+ pytest.skip("Skipping dbus-python based tests") - - from dbus import DBusException # noqa E402 - from apprise.plugins.NotifyDBus import DBusUrgency # noqa E402 diff --git a/packaging/redhat/python-apprise.spec b/packaging/redhat/python-apprise.spec index 71e69ed1..f4877cd7 100644 --- a/packaging/redhat/python-apprise.spec +++ b/packaging/redhat/python-apprise.spec @@ -21,14 +21,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. ############################################################################### -%global with_python2 1 -%global with_python3 1 - -%if 0%{?fedora} || 0%{?rhel} >= 8 -# Python v2 Support dropped -%global with_python2 0 -%endif - %if 0%{?_module_build} %bcond_with tests %else @@ -36,10 +28,6 @@ %bcond_without tests %endif -%if 0%{?rhel} && 0%{?rhel} <= 7 -%global with_python3 0 -%endif - %global pypi_name apprise %global common_description %{expand: \ @@ -61,85 +49,30 @@ Teams} Name: python-%{pypi_name} Version: 1.0.0 -Release: 2%{?dist} +Release: 3%{?dist} Summary: A simple wrapper to many popular notification services used today License: MIT URL: https://github.com/caronc/%{pypi_name} Source0: %{url}/archive/v%{version}/%{pypi_name}-%{version}.tar.gz -# this patch allows version of requests that ships with RHEL v7 to -# correctly handle test coverage. It also removes reference to a -# extra check not supported in py.test in EPEL7 builds -Patch0: %{pypi_name}-rhel7-support.patch -# CentOS/Rocky 7 and 8 ship with Click v6.7 which does not support the .stdout +# RHEL/Rocky 8 ship with Click v6.7 which does not support the .stdout # directive used in the unit testing. This patch just makes it so our package # continues to be compatible with these linux distributions -Patch1: %{pypi_name}-click67-support.patch +Patch0: %{pypi_name}-click67-support.patch BuildArch: noarch %description %{common_description} -%if 0%{?with_python2} -%package -n python2-%{pypi_name} -Summary: A simple wrapper to many popular notification services used today -%{?python_provide:%python_provide python2-%{pypi_name}} - -BuildRequires: python2-devel -BuildRequires: python-requests -BuildRequires: python2-requests-oauthlib -BuildRequires: python-six -BuildRequires: python2-click >= 5.0 -BuildRequires: python-markdown -%if 0%{?rhel} && 0%{?rhel} <= 7 -BuildRequires: python-cryptography -BuildRequires: python-babel -BuildRequires: python-yaml -%else -BuildRequires: python2-cryptography -BuildRequires: python2-babel -BuildRequires: python2-yaml -%endif - -Requires: python-requests -Requires: python2-requests-oauthlib -Requires: python-six -Requires: python-markdown -%if 0%{?rhel} && 0%{?rhel} <= 7 -Requires: python-cryptography -Requires: python-yaml -%else -Requires: python2-cryptography -Requires: python2-yaml -%endif - -%if %{with tests} -BuildRequires: python-mock -BuildRequires: python2-pytest-runner -BuildRequires: python2-pytest - -%endif - -%description -n python2-%{pypi_name} %{common_description} -%endif - %package -n %{pypi_name} Summary: Apprise CLI Tool -%if 0%{?with_python3} Requires: python%{python3_pkgversion}-click >= 5.0 Requires: python%{python3_pkgversion}-%{pypi_name} = %{version}-%{release} -%endif - -%if 0%{?with_python2} -Requires: python2-click >= 5.0 -Requires: python2-%{pypi_name} = %{version}-%{release} -%endif %description -n %{pypi_name} An accompanied CLI tool that can be used as part of Apprise to issue notifications from the command line to you favorite services. -%if 0%{?with_python3} %package -n python%{python3_pkgversion}-%{pypi_name} Summary: A simple wrapper to many popular notification services used today %{?python_provide:%python_provide python%{python3_pkgversion}-%{pypi_name}} @@ -148,7 +81,6 @@ BuildRequires: python%{python3_pkgversion}-devel BuildRequires: python%{python3_pkgversion}-setuptools BuildRequires: python%{python3_pkgversion}-requests BuildRequires: python%{python3_pkgversion}-requests-oauthlib -BuildRequires: python%{python3_pkgversion}-six BuildRequires: python%{python3_pkgversion}-click >= 5.0 BuildRequires: python%{python3_pkgversion}-markdown BuildRequires: python%{python3_pkgversion}-yaml @@ -156,7 +88,6 @@ BuildRequires: python%{python3_pkgversion}-babel BuildRequires: python%{python3_pkgversion}-cryptography Requires: python%{python3_pkgversion}-requests Requires: python%{python3_pkgversion}-requests-oauthlib -Requires: python%{python3_pkgversion}-six Requires: python%{python3_pkgversion}-markdown Requires: python%{python3_pkgversion}-cryptography Requires: python%{python3_pkgversion}-yaml @@ -173,93 +104,52 @@ BuildRequires: python%{python3_pkgversion}-pytest-runner %endif %description -n python%{python3_pkgversion}-%{pypi_name} %{common_description} -%endif %prep %setup -q -n %{pypi_name}-%{version} -%if 0%{?rhel} && 0%{?rhel} <= 7 -# rhel7 older package work-arounds -%patch0 -p1 -# rhel7 doesn't like the new asyncio syntax -rm -f apprise/py3compat/asyncio.py -%endif - %if 0%{?rhel} && 0%{?rhel} <= 8 -# click v6.7 unit testing support -%patch1 -p1 +# Rocky/RHEL 8 click v6.7 unit testing support +%patch0 -p1 %endif %if 0%{?rhel} >= 9 -# Nothing to do under normal circumstances; this line here allows legacy -# copies of Apprise to still build against this one -find test -type f -name '*.py' -exec \ - sed -i -e 's|^import mock|from unittest import mock|g' {} \; +# Do nothing %else -# support python-mock (remain backwards compatible with older distributions) +# CentOS 8.x requires python-mock (cororlates with import ab)ve find test -type f -name '*.py' -exec \ sed -i -e 's|^from unittest import mock|import mock|g' {} \; %endif %build -%if 0%{?with_python2} -%py2_build -%endif -%if 0%{?with_python3} %py3_build -%endif %install -%if 0%{?with_python2} -%py2_install -%endif -%if 0%{?with_python3} %py3_install -%endif install -p -D -T -m 0644 packaging/man/%{pypi_name}.1 \ %{buildroot}%{_mandir}/man1/%{pypi_name}.1 %if %{with tests} %check -%if 0%{?with_python2} -LANG=C.UTF-8 PYTHONPATH=%{buildroot}%{python2_sitelib} py.test -%endif -%if 0%{?with_python3} LANG=C.UTF-8 PYTHONPATH=%{buildroot}%{python3_sitelib} py.test-%{python3_version} %endif -%endif -%if 0%{?with_python2} -%files -n python2-%{pypi_name} -%license LICENSE -%doc README.md -%{python2_sitelib}/%{pypi_name} -%exclude %{python2_sitelib}/%{pypi_name}/cli.* -%{python2_sitelib}/*.egg-info -%endif - -%if 0%{?with_python3} %files -n python%{python3_pkgversion}-%{pypi_name} %license LICENSE %doc README.md %{python3_sitelib}/%{pypi_name} %exclude %{python3_sitelib}/%{pypi_name}/cli.* %{python3_sitelib}/*.egg-info -%endif %files -n %{pypi_name} %{_bindir}/%{pypi_name} %{_mandir}/man1/%{pypi_name}.1* - -%if 0%{?with_python3} %{python3_sitelib}/%{pypi_name}/cli.* -%endif - -%if 0%{?with_python2} -%{python2_sitelib}/%{pypi_name}/cli.* -%endif %changelog +* Fri Oct 7 2022 Chris Caron - 1.0.0-3 +- Python 2 Support dropped + * Wed Aug 31 2022 Chris Caron - 1.0.0-2 - Rebuilt for RHEL9 Support diff --git a/requirements.txt b/requirements.txt index 32510239..1cd0c763 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ requests requests-oauthlib -six click >= 5.0 markdown PyYAML diff --git a/setup.cfg b/setup.cfg index 34d9ed1a..80e6900d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,6 @@ python_files = test/test_*.py norecursedirs=test/helpers filterwarnings = once::Warning -strict = true [extract_messages] output-file = apprise/i18n/apprise.pot diff --git a/setup.py b/setup.py index 7fbb4925..b7183fc5 100755 --- a/setup.py +++ b/setup.py @@ -27,13 +27,7 @@ import re import os import platform -try: - from setuptools import setup - -except ImportError: - from distutils.core import setup - -from setuptools import find_packages +from setuptools import find_packages, setup cmdclass = {} try: @@ -94,13 +88,19 @@ setup( 'Operating System :: OS Independent', 'Natural Language :: English', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', 'License :: OSI Approved :: MIT License', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Application Frameworks', ), entry_points={'console_scripts': console_scripts}, - python_requires='>=2.7', + python_requires='>=3.6', setup_requires=['babel', ], ) diff --git a/test/helpers/module.py b/test/helpers/module.py index 47b3e9b4..d9c91a87 100644 --- a/test/helpers/module.py +++ b/test/helpers/module.py @@ -27,16 +27,7 @@ import re import os import sys -try: - # Python v3.4+ - from importlib import reload -except ImportError: - try: - # Python v3.0-v3.3 - from imp import reload - except ImportError: - # Python v2.7 - pass +from importlib import reload def module_reload(filename): diff --git a/test/helpers/rest.py b/test/helpers/rest.py index 3e4f0e77..2f143ce3 100644 --- a/test/helpers/rest.py +++ b/test/helpers/rest.py @@ -24,15 +24,8 @@ # THE SOFTWARE. import re import os -import six import requests -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from json import dumps from random import choice @@ -51,7 +44,7 @@ import logging logging.disable(logging.CRITICAL) -class AppriseURLTester(object): +class AppriseURLTester: # Some exception handling we'll use req_exceptions = ( @@ -151,7 +144,7 @@ class AppriseURLTester(object): # Allow us to force the server response text to be something other then # the defaults requests_response_text = meta.get('requests_response_text') - if not isinstance(requests_response_text, six.string_types): + if not isinstance(requests_response_text, str): # Convert to string requests_response_text = dumps(requests_response_text) @@ -242,11 +235,11 @@ class AppriseURLTester(object): # We loaded okay; now lets make sure we can reverse # this url - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Test url() with privacy=True assert isinstance( - obj.url(privacy=True), six.string_types) is True + obj.url(privacy=True), str) is True # Some Simple Invalid Instance Testing assert instance.parse_url(None) is None @@ -299,7 +292,7 @@ class AppriseURLTester(object): print('%s AssertionError' % url) raise - # Tidy our object and allow any possible defined deconstructors to + # Tidy our object and allow any possible defined destructors to # be executed. del obj @@ -346,7 +339,7 @@ class AppriseURLTester(object): # Allow us to force the server response text to be something other then # the defaults requests_response_text = meta.get('requests_response_text') - if not isinstance(requests_response_text, six.string_types): + if not isinstance(requests_response_text, str): # Convert to string requests_response_text = dumps(requests_response_text) diff --git a/test/test_api.py b/test/test_api.py index bc1d62e2..98e2718f 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -26,16 +26,9 @@ from __future__ import print_function import re import sys -import six import pytest import requests -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from os.path import dirname from os.path import join @@ -58,19 +51,14 @@ from apprise.plugins import __reset_matrix from apprise.utils import parse_list import inspect +# Sending notifications requires the coroutines to be awaited, so we need to +# wrap the original function when mocking it. +import apprise.py3compat.asyncio as py3aio + # Disable logging for a cleaner testing output import logging logging.disable(logging.CRITICAL) -# Sending notifications requires the coroutines to be awaited, so we need to -# wrap the original function when mocking it. But don't import for Python 2. -if not six.PY2: - import apprise.py3compat.asyncio as py3aio -else: - class py3aio: - def notify(): - pass - # Attachment Directory TEST_VAR_DIR = join(dirname(__file__), 'var') @@ -86,7 +74,6 @@ def test_apprise(): apprise_test(do_notify) -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") def test_apprise_async(): """ API: Apprise() object asynchronous methods @@ -154,12 +141,12 @@ def apprise_test(do_notify): assert len(a) == 2 # We can retrieve elements from our list too by reference: - assert isinstance(a[0].url(), six.string_types) is True + assert isinstance(a[0].url(), str) is True # We can iterate over our list too: count = 0 for o in a: - assert isinstance(o.url(), six.string_types) is True + assert isinstance(o.url(), str) is True count += 1 # verify that we did indeed iterate over each element assert len(a) == count @@ -547,7 +534,6 @@ def test_apprise_tagging(mock_post, mock_get): @mock.patch('requests.get') @mock.patch('requests.post') -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") def test_apprise_tagging_async(mock_post, mock_get): """ API: Apprise() object tagging functionality asynchronous methods @@ -669,7 +655,6 @@ def apprise_tagging_test(mock_post, mock_get, do_notify): tag=[(object, ), ]) is None -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") def test_apprise_schemas(tmpdir): """ API: Apprise().schema() tests @@ -918,20 +903,11 @@ def test_apprise_asset(tmpdir): must_exist=True) is not None # Test case where we can't access the image file - if sys.version_info.major <= 2: - # Python v2.x - with mock.patch('__builtin__.open', side_effect=OSError()): - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None - - # Our content is retrivable again - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None - else: - # Python >= v3.x - with mock.patch('builtins.open', side_effect=OSError()): - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None + with mock.patch('builtins.open', side_effect=OSError()): + assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None - # Our content is retrivable again - assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None + # Our content is retrivable again + assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None # Disable all image references a = AppriseAsset(image_path_mask=False, image_url_mask=False) @@ -1376,7 +1352,7 @@ def test_apprise_details(): assert 'details' in entry['requirements'] assert 'packages_required' in entry['requirements'] assert 'packages_recommended' in entry['requirements'] - assert isinstance(entry['requirements']['details'], six.string_types) + assert isinstance(entry['requirements']['details'], str) assert isinstance(entry['requirements']['packages_required'], list) assert isinstance(entry['requirements']['packages_recommended'], list) @@ -1403,7 +1379,7 @@ def test_apprise_details(): assert 'details' in entry['requirements'] assert 'packages_required' in entry['requirements'] assert 'packages_recommended' in entry['requirements'] - assert isinstance(entry['requirements']['details'], six.string_types) + assert isinstance(entry['requirements']['details'], str) assert isinstance(entry['requirements']['packages_required'], list) assert isinstance(entry['requirements']['packages_recommended'], list) @@ -1498,7 +1474,7 @@ def test_apprise_details_plugin_verification(): # A Service Name MUST be defined assert 'service_name' in entry assert isinstance( - entry['service_name'], (six.string_types, LazyTranslation)) + entry['service_name'], (str, LazyTranslation)) # Acquire our protocols protocols = parse_list( @@ -1527,10 +1503,10 @@ def test_apprise_details_plugin_verification(): if 'alias_of' not in arg: # Minimum requirement of an argument assert 'name' in arg - assert isinstance(arg['name'], six.string_types) + assert isinstance(arg['name'], str) assert 'type' in arg - assert isinstance(arg['type'], six.string_types) + assert isinstance(arg['type'], str) assert is_valid_type_re.match(arg['type']) is not None if 'min' in arg: @@ -1555,7 +1531,7 @@ def test_apprise_details_plugin_verification(): assert isinstance(arg['required'], bool) if 'prefix' in arg: - assert isinstance(arg['prefix'], six.string_types) + assert isinstance(arg['prefix'], str) if section == 'kwargs': # The only acceptable prefix types for kwargs assert arg['prefix'] in (':', '+', '-') @@ -1566,7 +1542,7 @@ def test_apprise_details_plugin_verification(): if 'map_to' in arg: # must be a string - assert isinstance(arg['map_to'], six.string_types) + assert isinstance(arg['map_to'], str) # Track our map_to object map_to_entries.add(arg['map_to']) @@ -1601,9 +1577,9 @@ def test_apprise_details_plugin_verification(): # Regex must ALWAYS be in the format (regex, option) assert isinstance(arg['regex'], (tuple, list)) assert len(arg['regex']) == 2 - assert isinstance(arg['regex'][0], six.string_types) + assert isinstance(arg['regex'][0], str) assert arg['regex'][1] is None or isinstance( - arg['regex'][1], six.string_types) + arg['regex'][1], str) # Compile the regular expression to verify that it is # valid @@ -1632,10 +1608,10 @@ def test_apprise_details_plugin_verification(): # must be a string assert isinstance( - arg['alias_of'], (six.string_types, list, tuple, set)) + arg['alias_of'], (str, list, tuple, set)) aliases = [arg['alias_of']] \ - if isinstance(arg['alias_of'], six.string_types) \ + if isinstance(arg['alias_of'], str) \ else arg['alias_of'] for alias_of in aliases: @@ -1687,7 +1663,7 @@ def test_apprise_details_plugin_verification(): # 'alias_of': ('apitoken', 'webtoken'), # }, # } - if isinstance(arg['alias_of'], six.string_types): + if isinstance(arg['alias_of'], str): assert len(entry['details'][section][key]) == 1 else: # is tuple,list, or set assert len(entry['details'][section][key]) == 2 @@ -1711,23 +1687,12 @@ def test_apprise_details_plugin_verification(): (tuple, set, list), ) - if six.PY2: - # inspect our object - # getargspec() is deprecated in Python v3 - spec = inspect.getargspec( - common.NOTIFY_SCHEMA_MAP[protocols[0]].__init__) - - function_args = \ - (set(parse_list(spec.keywords)) - set(['kwargs'])) \ - | (set(spec.args) - set(['self'])) | valid_kwargs - else: - # Python v3+ uses getfullargspec() - spec = inspect.getfullargspec( - common.NOTIFY_SCHEMA_MAP[protocols[0]].__init__) + spec = inspect.getfullargspec( + common.NOTIFY_SCHEMA_MAP[protocols[0]].__init__) - function_args = \ - (set(parse_list(spec.varkw)) - set(['kwargs'])) \ - | (set(spec.args) - set(['self'])) | valid_kwargs + function_args = \ + (set(parse_list(spec.varkw)) - set(['kwargs'])) \ + | (set(spec.args) - set(['self'])) | valid_kwargs # Iterate over our map_to_entries and make sure that everything # maps to a function argument @@ -1790,7 +1755,6 @@ def test_apprise_details_plugin_verification(): assert arg in defined_tokens -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") @mock.patch('requests.post') @mock.patch('apprise.py3compat.asyncio.notify', wraps=py3aio.notify) def test_apprise_async_mode(mock_async_notify, mock_post, tmpdir): @@ -1902,13 +1866,13 @@ def test_notify_matrix_dynamic_importing(tmpdir): # Test no app_id base.join('NotifyBadFile1.py').write( """ -class NotifyBadFile1(object): +class NotifyBadFile1: pass""") # No class of the same name base.join('NotifyBadFile2.py').write( """ -class BadClassName(object): +class BadClassName: pass""") # Exception thrown diff --git a/test/test_apprise_attachments.py b/test/test_apprise_attachments.py index 1b26ce84..d27afd7b 100644 --- a/test/test_apprise_attachments.py +++ b/test/test_apprise_attachments.py @@ -379,13 +379,13 @@ def test_attachment_matrix_dynamic_importing(tmpdir): # Test no app_id base.join('AttachBadFile1.py').write( """ -class AttachBadFile1(object): +class AttachBadFile1: pass""") # No class of the same name base.join('AttachBadFile2.py').write( """ -class BadClassName(object): +class BadClassName: pass""") # Exception thrown diff --git a/test/test_apprise_config.py b/test/test_apprise_config.py index 998e4174..6796b7ce 100644 --- a/test/test_apprise_config.py +++ b/test/test_apprise_config.py @@ -24,17 +24,8 @@ # THE SOFTWARE. import sys -import six -import io -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import pytest +from unittest import mock from apprise import NotifyFormat from apprise import ConfigFormat from apprise import ContentIncludeMode @@ -109,7 +100,7 @@ def test_apprise_config(tmpdir): assert len(ac.servers()) == 4 # Get our URL back - assert isinstance(ac[0].url(), six.string_types) + assert isinstance(ac[0].url(), str) # Test cases where our URL is invalid t = tmpdir.mkdir("strange-lines").join("apprise") @@ -156,13 +147,9 @@ def test_apprise_config(tmpdir): # I帽t毛rn芒ti么n脿lization Testing windows://""" - if six.PY2: - # decode string into unicode - istr = istr.decode('utf-8') - # Write our content to our file t = tmpdir.mkdir("internationalization").join("apprise") - with io.open(str(t), 'wb') as f: + with open(str(t), 'wb') as f: f.write(istr.encode('latin-1')) # Create ourselves a config object @@ -191,7 +178,7 @@ def test_apprise_config(tmpdir): assert len(ac.servers()) == 1 # Get our URL back - assert isinstance(ac[0].url(), six.string_types) + assert isinstance(ac[0].url(), str) # pop an entry from our list assert isinstance(ac.pop(0), ConfigBase) is True @@ -329,7 +316,7 @@ def test_apprise_add_config(): assert len(ac.servers()) == 3 # Get our URL back - assert isinstance(ac[0].url(), six.string_types) + assert isinstance(ac[0].url(), str) # Test invalid content assert ac.add_config(content=object()) is False @@ -1012,13 +999,13 @@ def test_configmatrix_dynamic_importing(tmpdir): # Test no app_id base.join('ConfigBadFile1.py').write( """ -class ConfigBadFile1(object): +class ConfigBadFile1: pass""") # No class of the same name base.join('ConfigBadFile2.py').write( """ -class BadClassName(object): +class BadClassName: pass""") # Exception thrown diff --git a/test/test_apprise_utils.py b/test/test_apprise_utils.py index 13e8f53e..c4b1f84a 100644 --- a/test/test_apprise_utils.py +++ b/test/test_apprise_utils.py @@ -27,15 +27,8 @@ from __future__ import print_function import re import os import sys -import six from inspect import cleandoc -try: - # Python 2.7 - from urllib import unquote - -except ImportError: - # Python 3.x - from urllib.parse import unquote +from urllib.parse import unquote from apprise import utils from apprise import common @@ -46,8 +39,6 @@ logging.disable(logging.CRITICAL) # Ensure we don't create .pyc files for these tests sys.dont_write_bytecode = True -# Python v2.x support requires an environment variable -os.environ["PYTHONDONTWRITEBYTECODE"] = "1" def test_parse_qsd(): @@ -1937,7 +1928,7 @@ def test_parse_list(): '.xvid', '.wmv', '.mp4', ]) - class StrangeObject(object): + class StrangeObject: def __str__(self): return '.avi' @@ -2541,39 +2532,39 @@ def test_apply_templating(): result = utils.apply_template( template, **{'fname': 'Chris', 'whence': 'this morning'}) - assert isinstance(result, six.string_types) is True + assert isinstance(result, str) is True assert result == "Hello Chris, How are you this morning?" # In this example 'whence' isn't provided, so it isn't swapped result = utils.apply_template( template, **{'fname': 'Chris'}) - assert isinstance(result, six.string_types) is True + assert isinstance(result, str) is True assert result == "Hello Chris, How are you {{whence}}?" # white space won't cause any ill affects: template = "Hello {{ fname }}, How are you {{ whence}}?" result = utils.apply_template( template, **{'fname': 'Chris', 'whence': 'this morning'}) - assert isinstance(result, six.string_types) is True + assert isinstance(result, str) is True assert result == "Hello Chris, How are you this morning?" # No arguments won't cause any problems template = "Hello {{fname}}, How are you {{whence}}?" result = utils.apply_template(template) - assert isinstance(result, six.string_types) is True + assert isinstance(result, str) is True assert result == template # Wrong elements are simply ignored result = utils.apply_template( template, **{'fname': 'l2g', 'whence': 'this evening', 'ignore': 'me'}) - assert isinstance(result, six.string_types) is True + assert isinstance(result, str) is True assert result == "Hello l2g, How are you this evening?" # Empty template makes things easy result = utils.apply_template( "", **{'fname': 'l2g', 'whence': 'this evening'}) - assert isinstance(result, six.string_types) is True + assert isinstance(result, str) is True assert result == "" # Regular expressions are safely escapped and act as normal diff --git a/test/test_asyncio.py b/test/test_asyncio.py index 7d542332..efa31858 100644 --- a/test/test_asyncio.py +++ b/test/test_asyncio.py @@ -24,7 +24,6 @@ # THE SOFTWARE. from __future__ import print_function -import six import sys import pytest from apprise import Apprise @@ -33,15 +32,14 @@ from apprise import NotifyFormat from apprise.common import NOTIFY_SCHEMA_MAP -if not six.PY2: - import apprise.py3compat.asyncio as py3aio +import apprise.py3compat.asyncio as py3aio # Disable logging for a cleaner testing output import logging logging.disable(logging.CRITICAL) -@pytest.mark.skipif(sys.version_info.major <= 2 or sys.version_info >= (3, 7), +@pytest.mark.skipif(sys.version_info >= (3, 7), reason="Requires Python 3.0 to 3.6") def test_apprise_asyncio_runtime_error(): """ @@ -112,7 +110,7 @@ def test_apprise_asyncio_runtime_error(): asyncio.set_event_loop(loop) -@pytest.mark.skipif(sys.version_info.major <= 2 or sys.version_info < (3, 7), +@pytest.mark.skipif(sys.version_info < (3, 7), reason="Requires Python 3.7+") def test_apprise_works_in_async_loop(): """ diff --git a/test/test_attach_base.py b/test/test_attach_base.py index 9bab8b0b..1e14ceb9 100644 --- a/test/test_attach_base.py +++ b/test/test_attach_base.py @@ -23,15 +23,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import pytest +from unittest import mock from apprise.attachment.AttachBase import AttachBase # Disable logging for a cleaner testing output diff --git a/test/test_attach_file.py b/test/test_attach_file.py index a4c335b8..a34d01e8 100644 --- a/test/test_attach_file.py +++ b/test/test_attach_file.py @@ -25,13 +25,7 @@ import re import time -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from os.path import dirname from os.path import join diff --git a/test/test_attach_http.py b/test/test_attach_http.py index 6a4761ae..afcf55e1 100644 --- a/test/test_attach_http.py +++ b/test/test_attach_http.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import re -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests import mimetypes @@ -140,7 +133,7 @@ def test_attach_http(mock_get): # Temporary path path = join(TEST_VAR_DIR, 'apprise-test.gif') - class DummyResponse(object): + class DummyResponse: """ A dummy response used to manage our object """ @@ -193,7 +186,7 @@ def test_attach_http(mock_get): 'http://user:pass@localhost/apprise.gif?dl=1&cache=300') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # Test that our extended variables are passed along assert mock_get.call_count == 0 @@ -210,7 +203,7 @@ def test_attach_http(mock_get): 'http://user:pass@localhost/apprise.gif?+key=value&cache=True') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -223,7 +216,7 @@ def test_attach_http(mock_get): 'http://localhost:3000/noname.gif?name=usethis.jpg&mime=image/jpeg') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # both mime and name over-ridden assert re.search(r'[?&]mime=image%2Fjpeg', attachment.url()) assert re.search(r'[?&]name=usethis.jpg', attachment.url()) @@ -257,7 +250,7 @@ def test_attach_http(mock_get): results = AttachHTTP.parse_url('http://localhost') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -279,7 +272,7 @@ def test_attach_http(mock_get): attachment = AttachHTTP(**results) # we can not download this attachment assert not attachment - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -298,7 +291,7 @@ def test_attach_http(mock_get): results = AttachHTTP.parse_url('http://localhost/no-length.gif') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -326,7 +319,7 @@ def test_attach_http(mock_get): results = AttachHTTP.parse_url('http://user@localhost/ignore-filename.gif') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -347,7 +340,7 @@ def test_attach_http(mock_get): attachment = AttachHTTP(**results) # we can not download this attachment assert not attachment - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -361,7 +354,7 @@ def test_attach_http(mock_get): results = AttachHTTP.parse_url('http://user@localhost') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -381,7 +374,7 @@ def test_attach_http(mock_get): results = AttachHTTP.parse_url('http://localhost/invalid-length.gif') assert isinstance(results, dict) attachment = AttachHTTP(**results) - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None @@ -399,7 +392,7 @@ def test_attach_http(mock_get): attachment = AttachHTTP(**results) # we can not download this attachment assert attachment - assert isinstance(attachment.url(), six.string_types) is True + assert isinstance(attachment.url(), str) is True # No mime-type and/or filename over-ride was specified, so therefore it # won't show up in the generated URL assert re.search(r'[?&]mime=', attachment.url()) is None diff --git a/test/test_cli.py b/test/test_cli.py index f3b1c80e..e739f9c7 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -24,13 +24,7 @@ # THE SOFTWARE. from __future__ import print_function import re -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests import json @@ -47,17 +41,8 @@ from apprise.utils import environ from apprise.plugins import __load_matrix from apprise.plugins import __reset_matrix +from importlib import reload -try: - # Python v3.4+ - from importlib import reload -except ImportError: - try: - # Python v3.0-v3.3 - from imp import reload - except ImportError: - # Python v2.7 - pass # Disable logging for a cleaner testing output import logging @@ -984,8 +969,7 @@ def test_apprise_cli_plugin_loading(mock_post, tmpdir): # We can find our new hook loaded in our NOTIFY_SCHEMA_MAP now... assert 'clihook' in NOTIFY_SCHEMA_MAP - # Store our key after parsing it as a list (this makes this test backwards - # compatible with Python 2.x + # Capture our key for reference key = [k for k in NOTIFY_CUSTOM_MODULE_MAP.keys()][0] assert len(NOTIFY_CUSTOM_MODULE_MAP[key]['notify']) == 1 @@ -1147,8 +1131,7 @@ def test_apprise_cli_plugin_loading(mock_post, tmpdir): assert 'clihook1' in NOTIFY_SCHEMA_MAP assert 'clihook2' in NOTIFY_SCHEMA_MAP - # Store our key after parsing it as a list (this makes this test backwards - # compatible with Python 2.x + # Capture our key for reference key = [k for k in NOTIFY_CUSTOM_MODULE_MAP.keys()][0] assert len(NOTIFY_CUSTOM_MODULE_MAP[key]['notify']) == 3 diff --git a/test/test_config_base.py b/test/test_config_base.py index f28d5f24..8d572a61 100644 --- a/test/test_config_base.py +++ b/test/test_config_base.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import pytest from apprise.AppriseAsset import AppriseAsset from apprise.config.ConfigBase import ConfigBase @@ -754,9 +753,9 @@ urls: assert asset.theme == AppriseAsset().theme # Empty string assignment - assert isinstance(asset.image_url_mask, six.string_types) is True + assert isinstance(asset.image_url_mask, str) is True assert asset.image_url_mask == "" - assert isinstance(asset.image_url_logo, six.string_types) is True + assert isinstance(asset.image_url_logo, str) is True assert asset.image_url_logo == "" # For on-lookers looking through this file; here is a perfectly formatted diff --git a/test/test_config_file.py b/test/test_config_file.py index 6ca5e4de..21351203 100644 --- a/test/test_config_file.py +++ b/test/test_config_file.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from apprise.config.ConfigFile import ConfigFile from apprise.plugins.NotifyBase import NotifyBase @@ -64,7 +57,7 @@ def test_config_file(tmpdir): # one entry added assert len(cf) == 1 - assert isinstance(cf.url(), six.string_types) is True + assert isinstance(cf.url(), str) is True # Verify that we're using the same asset assert cf[0].asset is asset @@ -102,8 +95,8 @@ def test_config_file(tmpdir): 'file://{}?cache=30'.format(str(t))) assert isinstance(results, dict) cf = ConfigFile(**results) - assert isinstance(cf.url(), six.string_types) is True - assert isinstance(cf.read(), six.string_types) is True + assert isinstance(cf.url(), str) is True + assert isinstance(cf.read(), str) is True def test_config_file_exceptions(tmpdir): @@ -120,7 +113,7 @@ def test_config_file_exceptions(tmpdir): cf = ConfigFile(path=str(t), format='text') # Internal Exception would have been thrown and this would fail - with mock.patch('io.open', side_effect=OSError): + with mock.patch('builtins.open', side_effect=OSError): assert cf.read() is None # handle case where the file is to large for what was expected: diff --git a/test/test_config_http.py b/test/test_config_http.py index ee68a096..e0a1dae1 100644 --- a/test/test_config_http.py +++ b/test/test_config_http.py @@ -23,16 +23,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import time import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise.common import ConfigFormat @@ -86,7 +79,7 @@ def test_config_http(mock_post): # Our default content default_content = """taga,tagb=good://server01""" - class DummyResponse(object): + class DummyResponse: """ A dummy response used to manage our object """ @@ -122,8 +115,8 @@ def test_config_http(mock_post): results = ConfigHTTP.parse_url('http://user:pass@localhost?+key=value') assert isinstance(results, dict) ch = ConfigHTTP(**results) - assert isinstance(ch.url(), six.string_types) is True - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.url(), str) is True + assert isinstance(ch.read(), str) is True # one entry added assert len(ch) == 1 @@ -131,8 +124,8 @@ def test_config_http(mock_post): results = ConfigHTTP.parse_url('http://localhost:8080/path/') assert isinstance(results, dict) ch = ConfigHTTP(**results) - assert isinstance(ch.url(), six.string_types) is True - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.url(), str) is True + assert isinstance(ch.read(), str) is True # one entry added assert len(ch) == 1 @@ -143,16 +136,16 @@ def test_config_http(mock_post): # Cache Handling; cache each request for 30 seconds results = ConfigHTTP.parse_url('http://localhost:8080/path/?cache=30') assert mock_post.call_count == 0 - assert isinstance(ch.url(), six.string_types) is True + assert isinstance(ch.url(), str) is True assert isinstance(results, dict) ch = ConfigHTTP(**results) assert mock_post.call_count == 0 - assert isinstance(ch.url(), six.string_types) is True + assert isinstance(ch.url(), str) is True assert mock_post.call_count == 0 - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True assert mock_post.call_count == 1 # Clear all our mock counters @@ -210,7 +203,7 @@ def test_config_http(mock_post): # Invalid cache results = ConfigHTTP.parse_url('http://localhost:8080/path/?cache=False') assert isinstance(results, dict) - assert isinstance(ch.url(), six.string_types) is True + assert isinstance(ch.url(), str) is True results = ConfigHTTP.parse_url('http://localhost:8080/path/?cache=-10') assert isinstance(results, dict) @@ -220,8 +213,8 @@ def test_config_http(mock_post): results = ConfigHTTP.parse_url('http://user@localhost?format=text') assert isinstance(results, dict) ch = ConfigHTTP(**results) - assert isinstance(ch.url(), six.string_types) is True - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.url(), str) is True + assert isinstance(ch.read(), str) is True # one entry added assert len(ch) == 1 @@ -229,8 +222,8 @@ def test_config_http(mock_post): results = ConfigHTTP.parse_url('https://localhost') assert isinstance(results, dict) ch = ConfigHTTP(**results) - assert isinstance(ch.url(), six.string_types) is True - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.url(), str) is True + assert isinstance(ch.read(), str) is True # one entry added assert len(ch) == 1 @@ -265,7 +258,7 @@ def test_config_http(mock_post): # Test a buffer size limit reach ch.max_buffer_size = len(dummy_response.text) - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # Test YAML detection yaml_supported_types = ( @@ -274,7 +267,7 @@ def test_config_http(mock_post): for st in yaml_supported_types: dummy_response.headers['Content-Type'] = st ch.default_config_format = None - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # Set to YAML assert ch.default_config_format == ConfigFormat.YAML @@ -284,7 +277,7 @@ def test_config_http(mock_post): for st in text_supported_types: dummy_response.headers['Content-Type'] = st ch.default_config_format = None - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # Set to TEXT assert ch.default_config_format == ConfigFormat.TEXT @@ -294,14 +287,14 @@ def test_config_http(mock_post): for st in ukwn_supported_types: dummy_response.headers['Content-Type'] = st ch.default_config_format = None - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # Remains unchanged assert ch.default_config_format is None # When the entry is missing; we handle this too del dummy_response.headers['Content-Type'] ch.default_config_format = None - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # Remains unchanged assert ch.default_config_format is None @@ -321,16 +314,16 @@ def test_config_http(mock_post): # Our content is still within the limits, so we're okay dummy_response.headers['Content-Length'] = 'garbage' - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True dummy_response.headers['Content-Length'] = 'None' # Our content is still within the limits, so we're okay - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # Handle cases where the content length is exactly at our limit dummy_response.text = 'a' * ch.max_buffer_size # This is acceptable - assert isinstance(ch.read(), six.string_types) is True + assert isinstance(ch.read(), str) is True # If we are over our limit though.. dummy_response.text = 'b' * (ch.max_buffer_size + 1) diff --git a/test/test_config_memory.py b/test/test_config_memory.py index 0bc84e8a..546ff986 100644 --- a/test/test_config_memory.py +++ b/test/test_config_memory.py @@ -23,7 +23,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six from apprise.config.ConfigMemory import ConfigMemory # Disable logging for a cleaner testing output @@ -46,8 +45,8 @@ def test_config_memory(): assert len(cm) == 1 # Test general functions - assert isinstance(cm.url(), six.string_types) is True - assert isinstance(cm.read(), six.string_types) is True + assert isinstance(cm.url(), str) is True + assert isinstance(cm.read(), str) is True # Test situation where an auto-detect is required: cm = ConfigMemory(content="syslog://") @@ -56,8 +55,8 @@ def test_config_memory(): assert len(cm) == 1 # Test general functions - assert isinstance(cm.url(), six.string_types) is True - assert isinstance(cm.read(), six.string_types) is True + assert isinstance(cm.url(), str) is True + assert isinstance(cm.read(), str) is True # Test situation where we can not detect the data assert len(ConfigMemory(content="garbage")) == 0 diff --git a/test/test_custom_json_plugin.py b/test/test_custom_json_plugin.py index e132b356..7edc891d 100644 --- a/test/test_custom_json_plugin.py +++ b/test/test_custom_json_plugin.py @@ -25,13 +25,7 @@ import os import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins diff --git a/test/test_escapes.py b/test/test_escapes.py index 8a32b1c6..9e2d2cd0 100644 --- a/test/test_escapes.py +++ b/test/test_escapes.py @@ -23,17 +23,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from __future__ import print_function -import sys from json import loads -import pytest import requests -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import apprise @@ -41,7 +33,7 @@ import apprise @mock.patch('requests.post') def test_apprise_interpret_escapes(mock_post): """ - API: Apprise() interpret-escapse tests + API: Apprise() interpret-escape tests """ # Prepare Mock @@ -129,11 +121,10 @@ def test_apprise_interpret_escapes(mock_post): .get('message', '') == 'ab\\ncd' -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") @mock.patch('requests.post') -def test_apprise_escaping_py3(mock_post): +def test_apprise_escaping(mock_post): """ - API: Apprise() Python v3.x escaping + API: Apprise() escaping tests """ a = apprise.Apprise() @@ -235,132 +226,3 @@ def test_apprise_escaping_py3(mock_post): # Bytes are supported assert a.notify( title=b'byte title', body="valid", interpret_escapes=True) is True - - -@pytest.mark.skipif(sys.version_info.major >= 3, reason="Requires Python 2.x+") -@mock.patch('requests.post') -def test_apprise_escaping_py2(mock_post): - """ - API: Apprise() Python v2.x escaping - - """ - a = apprise.Apprise() - - response = mock.Mock() - response.content = '' - response.status_code = requests.codes.ok - mock_post.return_value = response - - # Create ourselves a test object to work with - a.add('json://localhost') - - # Escape our content - assert a.notify( - title="\\r\\ntitle\\r\\n", body="\\r\\nbody\\r\\n", - interpret_escapes=True) - - # Verify our content was escaped correctly - assert mock_post.call_count == 1 - result = loads(mock_post.call_args_list[0][1]['data']) - assert result['title'] == 'title' - assert result['message'] == '\r\nbody' - - # Reset our mock object - mock_post.reset_mock() - - # - # Support Specially encoded content: - # - - # Escape our content - assert a.notify( - # Google Translated to Arabic: "Let's make the world a better place." - title='丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.\\r\\t\\t\\n\\r\\n', - # Google Translated to Hungarian: "One line of code at a time.' - body='Egy sor k贸dot egyszerre.\\r\\n\\r\\r\\n', - # Our Escape Flag - interpret_escapes=True) - - # Verify our content was escaped correctly - assert mock_post.call_count == 1 - result = loads(mock_post.call_args_list[0][1]['data']) - - expected = '丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.' - assert result['title'] in ( - expected, - # Older versions of Python (such as 2.7.5) provide this value as - # a type unicode. This specifically happens when building RPMs - # for RedHat/CentOS 7. The internal RPM unit tests fail without this: - expected.decode('utf-8'), - ) - - expected = 'Egy sor k贸dot egyszerre.' - assert result['message'] in ( - expected, - # Older versions of Python (such as 2.7.5) provide this value as - # a type unicode. This specifically happens when building RPMs - # for RedHat/CentOS 7. The internal RPM unit tests fail without this: - expected.decode('utf-8'), - ) - - # Reset our status - mock_post.reset_mock() - - # Use unicode characters - assert a.notify( - # Google Translated to Arabic: "Let's make the world a better place." - title='丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.\\r\\t\\t\\n\\r\\n'.decode( - 'utf-8'), - # Google Translated to Hungarian: "One line of code at a time.' - body='Egy sor k贸dot egyszerre.\\r\\n\\r\\r\\n'.decode( - 'utf-8'), - # Our Escape Flag - interpret_escapes=True) - - # Verify our content was escaped correctly - assert mock_post.call_count == 1 - result = loads(mock_post.call_args_list[0][1]['data']) - - expected = '丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.' - assert result['title'] in ( - expected, - # Older versions of Python (such as 2.7.5) provide this value as - # a type unicode. This specifically happens when building RPMs - # for RedHat/CentOS 7. The internal RPM unit tests fail without this: - expected.decode('utf-8'), - ) - - expected = 'Egy sor k贸dot egyszerre.' - assert result['message'] in ( - expected, - # Older versions of Python (such as 2.7.5) provide this value as - # a type unicode. This specifically happens when building RPMs - # for RedHat/CentOS 7. The internal RPM unit tests fail without this: - expected.decode('utf-8'), - ) - - # Error handling - # - # We can't escape the content below - assert a.notify( - title=None, body=4, interpret_escapes=True) is False - assert a.notify( - title=4, body=None, interpret_escapes=True) is False - assert a.notify( - title=4, body="valid body", interpret_escapes=True) is False - assert a.notify( - title=object(), body=False, interpret_escapes=True) is False - assert a.notify( - title=False, body=object(), interpret_escapes=True) is False - - # The body is proessed first, so the errors thrown above get tested on - # the body only. Now we run similar tests but only make the title - # bad and always mark the body good - assert a.notify( - title=None, body="valid", interpret_escapes=True) is True - assert a.notify( - title=4, body="valid", interpret_escapes=True) is False - assert a.notify( - title=object(), body="valid", interpret_escapes=True) is False - assert a.notify( - title=False, body="valid", interpret_escapes=True) is True diff --git a/test/test_locale.py b/test/test_locale.py index 49f8e3fb..77accd4b 100644 --- a/test/test_locale.py +++ b/test/test_locale.py @@ -24,29 +24,14 @@ # THE SOFTWARE. import os -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import ctypes from apprise import AppriseLocale from apprise.utils import environ +from importlib import reload -try: - # Python v3.4+ - from importlib import reload -except ImportError: - try: - # Python v3.0-v3.3 - from imp import reload - except ImportError: - # Python v2.7 - pass # Disable logging for a cleaner testing output import logging @@ -177,9 +162,11 @@ def test_detect_language_windows_users(): # Detect french language assert AppriseLocale.AppriseLocale.detect_language() == 'fr' - # The following unsets all enviroment vaiables and sets LC_CTYPE + # The following unsets all environment variables and sets LC_CTYPE # This was causing Python 2.7 to internally parse UTF-8 as an invalid - # locale and throw an uncaught ValueError + # locale and throw an uncaught ValueError; Python v2 support has been + # dropped, but just to ensure this issue does not come back, we keep + # this test: with environ(*list(os.environ.keys()), LC_CTYPE="UTF-8"): assert AppriseLocale.AppriseLocale.detect_language() is None diff --git a/test/test_logger.py b/test/test_logger.py index ebf1a7f7..cd6bdff6 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -25,17 +25,10 @@ import re import os -import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import pytest import requests +from unittest import mock + from apprise import Apprise from apprise import AppriseAsset from apprise import URLBase @@ -77,10 +70,9 @@ def test_apprise_logger(): logging.disable(logging.CRITICAL) -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") -def test_apprise_log_memory_captures_py3(): +def test_apprise_log_memory_captures(): """ - API: Apprise() Python v3 Log Captures + API: Apprise() Log Memory Captures """ @@ -220,153 +212,9 @@ def test_apprise_log_memory_captures_py3(): logging.disable(logging.CRITICAL) -@pytest.mark.skipif(sys.version_info.major >= 3, reason="Requires Python 2.x+") -def test_apprise_log_memory_captures_py2(): - """ - API: Apprise() Python v2 Log Captures - - """ - - # Ensure we're not running in a disabled state - logging.disable(logging.NOTSET) - - logger.setLevel(logging.CRITICAL) - with LogCapture(level=logging.TRACE) as stream: - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - logs = re.split(r'\r*\n', stream.getvalue().rstrip()) - - # We have a log entry for each of the 6 logs we generated above - assert 'trace' in stream.getvalue() - assert 'debug' in stream.getvalue() - assert 'info' in stream.getvalue() - assert 'warning' in stream.getvalue() - assert 'error' in stream.getvalue() - assert 'deprecate' in stream.getvalue() - assert len(logs) == 6 - - # Verify that we did not lose our effective log level even though - # the above steps the level up for the duration of the capture - assert logger.getEffectiveLevel() == logging.CRITICAL - - logger.setLevel(logging.TRACE) - with LogCapture(level=logging.DEBUG) as stream: - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - # We have a log entry for 5 of the log entries we generated above - # There will be no 'trace' entry - assert 'trace' not in stream.getvalue() - assert 'debug' in stream.getvalue() - assert 'info' in stream.getvalue() - assert 'warning' in stream.getvalue() - assert 'error' in stream.getvalue() - assert 'deprecate' in stream.getvalue() - - logs = re.split(r'\r*\n', stream.getvalue().rstrip()) - assert len(logs) == 5 - - # Verify that we did not lose our effective log level even though - # the above steps the level up for the duration of the capture - assert logger.getEffectiveLevel() == logging.TRACE - - logger.setLevel(logging.ERROR) - with LogCapture(level=logging.WARNING) as stream: - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - # We have a log entry for 3 of the log entries we generated above - # There will be no 'trace', 'debug', or 'info' entry - assert 'trace' not in stream.getvalue() - assert 'debug' not in stream.getvalue() - assert 'info' not in stream.getvalue() - assert 'warning' in stream.getvalue() - assert 'error' in stream.getvalue() - assert 'deprecate' in stream.getvalue() - - logs = re.split(r'\r*\n', stream.getvalue().rstrip()) - assert len(logs) == 3 - - # Set a global level of ERROR - logger.setLevel(logging.ERROR) - - # Use the default level of None (by not specifying one); we then - # use whatever has been defined globally - with LogCapture() as stream: - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - assert 'trace' not in stream.getvalue() - assert 'debug' not in stream.getvalue() - assert 'info' not in stream.getvalue() - assert 'warning' not in stream.getvalue() - assert 'error' in stream.getvalue() - assert 'deprecate' in stream.getvalue() - - logs = re.split(r'\r*\n', stream.getvalue().rstrip()) - assert len(logs) == 2 - - # Verify that we did not lose our effective log level - assert logger.getEffectiveLevel() == logging.ERROR - - with LogCapture(level=logging.TRACE) as stream: - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - # We have a log entry for each of the 6 logs we generated above - assert 'trace' in stream.getvalue() - assert 'debug' in stream.getvalue() - assert 'info' in stream.getvalue() - assert 'warning' in stream.getvalue() - assert 'error' in stream.getvalue() - assert 'deprecate' in stream.getvalue() - - logs = re.split(r'\r*\n', stream.getvalue().rstrip()) - assert len(logs) == 6 - - # Verify that we did not lose our effective log level even though - # the above steps the level up for the duration of the capture - assert logger.getEffectiveLevel() == logging.ERROR - - # Test capture where our notification throws an unhandled exception - obj = Apprise.instantiate('json://user:password@example.com') - with mock.patch('requests.post', side_effect=NotImplementedError()): - with pytest.raises(NotImplementedError): - # Our exception gets caught in side our with() block - # and although raised, all graceful handling of the log - # is reverted as it was - with LogCapture(level=logging.TRACE) as stream: - obj.send("hello world") - - # Disable Logging - logging.disable(logging.CRITICAL) - - -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") -def test_apprise_log_file_captures_py3(tmpdir): +def test_apprise_log_file_captures(tmpdir): """ - API: Apprise() Python v3 Logfile Captures + API: Apprise() Log File Captures """ @@ -497,141 +345,6 @@ def test_apprise_log_file_captures_py3(tmpdir): logging.disable(logging.CRITICAL) -@pytest.mark.skipif(sys.version_info.major >= 3, reason="Requires Python 2.x+") -def test_apprise_log_file_captures_py2(tmpdir): - """ - API: Apprise() Python v2 Logfile Captures - - """ - - # Ensure we're not running in a disabled state - logging.disable(logging.NOTSET) - - log_file = tmpdir.join('capture.log') - assert not os.path.isfile(str(log_file)) - - logger.setLevel(logging.CRITICAL) - with LogCapture(path=str(log_file), level=logging.TRACE) as fp: - # The file will exit now - assert os.path.isfile(str(log_file)) - - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - content = fp.read().rstrip() - logs = re.split(r'\r*\n', content) - - # We have a log entry for each of the 6 logs we generated above - assert 'trace' in content - assert 'debug' in content - assert 'info' in content - assert 'warning' in content - assert 'error' in content - assert 'deprecate' in content - assert len(logs) == 6 - - # The file is automatically cleaned up afterwards - assert not os.path.isfile(str(log_file)) - - # Verify that we did not lose our effective log level even though - # the above steps the level up for the duration of the capture - assert logger.getEffectiveLevel() == logging.CRITICAL - - logger.setLevel(logging.TRACE) - with LogCapture(path=str(log_file), level=logging.DEBUG) as fp: - # The file will exit now - assert os.path.isfile(str(log_file)) - - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - content = fp.read().rstrip() - logs = re.split(r'\r*\n', content) - - # We have a log entry for 5 of the log entries we generated above - # There will be no 'trace' entry - assert 'trace' not in content - assert 'debug' in content - assert 'info' in content - assert 'warning' in content - assert 'error' in content - assert 'deprecate' in content - - assert len(logs) == 5 - - # Remove our file before we exit the with clause - # this causes our delete() call to throw gracefully inside - os.unlink(str(log_file)) - - # Verify file is gone - assert not os.path.isfile(str(log_file)) - - # Verify that we did not lose our effective log level even though - # the above steps the level up for the duration of the capture - assert logger.getEffectiveLevel() == logging.TRACE - - logger.setLevel(logging.ERROR) - with LogCapture(path=str(log_file), delete=False, - level=logging.WARNING) as fp: - - # Verify exists - assert os.path.isfile(str(log_file)) - - logger.trace(u"trace") - logger.debug(u"debug") - logger.info(u"info") - logger.warning(u"warning") - logger.error(u"error") - logger.deprecate(u"deprecate") - - content = fp.read().rstrip() - logs = re.split(r'\r*\n', content) - - # We have a log entry for 3 of the log entries we generated above - # There will be no 'trace', 'debug', or 'info' entry - assert 'trace' not in content - assert 'debug' not in content - assert 'info' not in content - assert 'warning' in content - assert 'error' in content - assert 'deprecate' in content - - assert len(logs) == 3 - - # Verify the file still exists (because delete was set to False) - assert os.path.isfile(str(log_file)) - - # remove it now - os.unlink(str(log_file)) - - # Enure it's been removed - assert not os.path.isfile(str(log_file)) - - # Set a global level of ERROR - logger.setLevel(logging.ERROR) - - # Test case where we can't open the file - with mock.patch('__builtin__.open', side_effect=OSError()): - # Use the default level of None (by not specifying one); we then - # use whatever has been defined globally - with pytest.raises(OSError): - with LogCapture(path=str(log_file)) as fp: - # we'll never get here because we'll fail to open the file - pass - - # Disable Logging - logging.disable(logging.CRITICAL) - - -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") @mock.patch('requests.post') def test_apprise_secure_logging(mock_post): """ diff --git a/test/test_notify_base.py b/test/test_notify_base.py index 2ad14462..66092fd8 100644 --- a/test/test_notify_base.py +++ b/test/test_notify_base.py @@ -22,7 +22,6 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import pytest from datetime import datetime from datetime import timedelta @@ -156,7 +155,7 @@ def test_notify_base(): assert nb.color(notify_type='invalid') is None assert isinstance( nb.color(notify_type=NotifyType.INFO, color_type=None), - six.string_types) + str) assert isinstance( nb.color(notify_type=NotifyType.INFO, color_type=int), int) assert isinstance( diff --git a/test/test_plugin_boxcar.py b/test/test_plugin_boxcar.py index 4b69d24d..11e9714e 100644 --- a/test/test_plugin_boxcar.py +++ b/test/test_plugin_boxcar.py @@ -23,13 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from helpers import AppriseURLTester from apprise import plugins diff --git a/test/test_plugin_custom_form.py b/test/test_plugin_custom_form.py index ad169d8f..68a5ed32 100644 --- a/test/test_plugin_custom_form.py +++ b/test/test_plugin_custom_form.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os -import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -193,13 +186,6 @@ def test_plugin_custom_form_attachments(mock_post): body='body', title='title', notify_type=NotifyType.INFO, attach=attach) is False - # Get a appropriate "builtin" module name for pythons 2/3. - if sys.version_info.major >= 3: - builtin_open_function = 'builtins.open' - - else: - builtin_open_function = '__builtin__.open' - # Test Valid Attachment (load 3) path = ( os.path.join(TEST_VAR_DIR, 'apprise-test.gif'), @@ -211,14 +197,14 @@ def test_plugin_custom_form_attachments(mock_post): # Return our good configuration mock_post.side_effect = None mock_post.return_value = okay_response - with mock.patch(builtin_open_function, side_effect=OSError()): + with mock.patch('builtins.open', side_effect=OSError()): # We can't send the message we can't open the attachment for reading assert obj.notify( body='body', title='title', notify_type=NotifyType.INFO, attach=attach) is False # Fail on the 2nd attempt (but not the first) - with mock.patch(builtin_open_function, + with mock.patch('builtins.open', side_effect=[None, OSError(), None]): # We can't send the message we can't open the attachment for reading assert obj.notify( diff --git a/test/test_plugin_custom_json.py b/test/test_plugin_custom_json.py index 620278a8..b238e6ed 100644 --- a/test/test_plugin_custom_json.py +++ b/test/test_plugin_custom_json.py @@ -23,13 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import json -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins diff --git a/test/test_plugin_custom_xml.py b/test/test_plugin_custom_xml.py index d1e385c9..8d0510fd 100644 --- a/test/test_plugin_custom_xml.py +++ b/test/test_plugin_custom_xml.py @@ -23,15 +23,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os -import sys import re -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -198,13 +191,6 @@ def test_notify_xml_plugin_attachments(mock_post): body='body', title='title', notify_type=NotifyType.INFO, attach=path) is False - # Get a appropriate "builtin" module name for pythons 2/3. - if sys.version_info.major >= 3: - builtin_open_function = 'builtins.open' - - else: - builtin_open_function = '__builtin__.open' - # Test Valid Attachment (load 3) path = ( os.path.join(TEST_VAR_DIR, 'apprise-test.gif'), @@ -216,7 +202,7 @@ def test_notify_xml_plugin_attachments(mock_post): # Return our good configuration mock_post.side_effect = None mock_post.return_value = okay_response - with mock.patch(builtin_open_function, side_effect=OSError()): + with mock.patch('builtins.open', side_effect=OSError()): # We can't send the message we can't open the attachment for reading assert obj.notify( body='body', title='title', notify_type=NotifyType.INFO, @@ -291,7 +277,7 @@ def test_plugin_custom_xml_edge_cases(mock_get, mock_post): assert new_results[k] == results[k] # Test our data set for our key/value pair - assert re.search('[1-9]+\.[0-9]+', details[1]['data']) + assert re.search(r'[1-9]+\.[0-9]+', details[1]['data']) assert re.search('info', details[1]['data']) assert re.search('title', details[1]['data']) # Custom entry Message acts as Over-ride and kicks in here diff --git a/test/test_plugin_dapnet.py b/test/test_plugin_dapnet.py index eb218d5f..82351651 100644 --- a/test/test_plugin_dapnet.py +++ b/test/test_plugin_dapnet.py @@ -25,14 +25,7 @@ # Disable logging for a cleaner testing output import logging import requests -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - +from unittest import mock import apprise from apprise.plugins.NotifyDapnet import DapnetPriority diff --git a/test/test_plugin_discord.py b/test/test_plugin_discord.py index 0df36c86..db14f442 100644 --- a/test/test_plugin_discord.py +++ b/test/test_plugin_discord.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests @@ -211,7 +204,7 @@ def test_plugin_discord_general(mock_post): footer=True, thumbnail=False) # Test that we get a string response - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # This call includes an image with it's payload: assert obj.notify( @@ -240,7 +233,7 @@ def test_plugin_discord_general(mock_post): desc, results = obj.extract_markdown_sections(test_markdown) # we have a description - assert isinstance(desc, six.string_types) is True + assert isinstance(desc, str) is True assert desc.startswith('A section of text that has no header at the top.') assert desc.endswith('string.') @@ -265,7 +258,7 @@ def test_plugin_discord_general(mock_post): desc, results = obj.extract_markdown_sections(test_markdown) assert isinstance(results, list) is True # No desc details filled out - assert isinstance(desc, six.string_types) is True + assert isinstance(desc, str) is True assert not desc # We should have 5 sections (since there are 5 headers identified above) @@ -325,7 +318,7 @@ def test_plugin_discord_general(mock_post): assert len(results) == 0 # No desc details filled out - assert isinstance(desc, six.string_types) is True + assert isinstance(desc, str) is True assert not desc # String without Heading @@ -336,7 +329,7 @@ def test_plugin_discord_general(mock_post): assert len(results) == 0 # No desc details filled out - assert isinstance(desc, six.string_types) is True + assert isinstance(desc, str) is True assert desc == 'Just a string without any header entries.\n' + \ 'A second line' diff --git a/test/test_plugin_email.py b/test/test_plugin_email.py index 8c9ada0d..c7b755b8 100644 --- a/test/test_plugin_email.py +++ b/test/test_plugin_email.py @@ -25,14 +25,7 @@ import os import re -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import smtplib from email.header import decode_header @@ -330,11 +323,11 @@ def test_plugin_email(mock_smtp, mock_smtpssl): if isinstance(obj, plugins.NotifyBase): # We loaded okay; now lets make sure we can reverse this url - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Test url() with privacy=True assert isinstance( - obj.url(privacy=True), six.string_types) is True + obj.url(privacy=True), str) is True # Some Simple Invalid Instance Testing assert instance.parse_url(None) is None @@ -564,7 +557,7 @@ def test_plugin_email_smtplib_internationalization(mock_smtp): suppress_exceptions=False) assert isinstance(obj, plugins.NotifyEmail) - class SMTPMock(object): + class SMTPMock: def sendmail(self, *args, **kwargs): """ over-ride sendmail calls so we can check our our @@ -584,19 +577,11 @@ def test_plugin_email_smtplib_internationalization(mock_smtp): # Verify our output was correctly stored assert match_from.group('email') == 'user@gmail.com' - if six.PY2: # Python 2.x (backwards compatible) - assert decode_header(match_from.group('name'))[0][0]\ - .decode('utf-8') == u'袧邪锌褉懈屑械褉 褌邪泻' - - assert decode_header(match_subject.group('subject'))[0][0]\ - .decode('utf-8') == u'丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.' - - else: # Python 3+ - assert decode_header(match_from.group('name'))[0][0]\ - .decode('utf-8') == '袧邪锌褉懈屑械褉 褌邪泻' + assert decode_header(match_from.group('name'))[0][0]\ + .decode('utf-8') == '袧邪锌褉懈屑械褉 褌邪泻' - assert decode_header(match_subject.group('subject'))[0][0]\ - .decode('utf-8') == '丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.' + assert decode_header(match_subject.group('subject'))[0][0]\ + .decode('utf-8') == '丿毓賵賳丕 賳噩毓賱 丕賱毓丕賱賲 賲賰丕賳丕 兀賮囟賱.' # Dummy Function def quit(self, *args, **kwargs): diff --git a/test/test_plugin_emby.py b/test/test_plugin_emby.py index 05597329..1d566ab3 100644 --- a/test/test_plugin_emby.py +++ b/test/test_plugin_emby.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from json import dumps from apprise import Apprise diff --git a/test/test_plugin_fcm.py b/test/test_plugin_fcm.py index ffe85d18..22e9f82d 100644 --- a/test/test_plugin_fcm.py +++ b/test/test_plugin_fcm.py @@ -29,17 +29,9 @@ # - Legacy API (v1) -> OAuth # - https://firebase.google.com/docs/cloud-messaging/migrate-v1 -import io import os -import six import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests @@ -60,12 +52,6 @@ except ImportError: # No problem; there is no cryptography support pass -try: - from json.decoder import JSONDecodeError - -except ImportError: - # Python v2.7 Backwards Compatibility support - JSONDecodeError = ValueError # Disable logging for a cleaner testing output import logging @@ -218,6 +204,8 @@ def test_plugin_fcm_urls(): @pytest.mark.skipif( 'cryptography' not in sys.modules, reason="Requires cryptography") +@pytest.mark.skipif( + hasattr(sys, "pypy_version_info"), reason="Does not work reliably on PyPy") @mock.patch('requests.post') def test_plugin_fcm_general_legacy(mock_post): """ @@ -381,7 +369,7 @@ def test_plugin_fcm_general_oauth(mock_post): obj = Apprise.instantiate( 'fcm://mock-project-id/device/?keyfile={}'.format(str(path))) - with mock.patch('io.open', side_effect=OSError): + with mock.patch('builtins.open', side_effect=OSError): # we'll fail as a result assert obj.notify("test") is False @@ -617,7 +605,7 @@ def test_plugin_fcm_keyfile_parse(mock_post): # Now we test a case where we can't access the file we've been pointed to: oauth = GoogleOAuth() - with mock.patch('io.open', side_effect=OSError): + with mock.patch('builtins.open', side_effect=OSError): # We will fail to retrieve our Access Token assert oauth.load(path) is False assert oauth.access_token is None @@ -722,7 +710,7 @@ def test_plugin_fcm_keyfile_missing_entries_parse(tmpdir): # Prepare a base keyfile reference to use path = os.path.join(PRIVATE_KEYFILE_DIR, 'service_account.json') - with io.open(path, mode="r", encoding='utf-8') as fp: + with open(path, mode="r", encoding='utf-8') as fp: content = json.loads(fp.read()) path = tmpdir.join('fcm_keyfile.json') @@ -804,7 +792,7 @@ def test_plugin_fcm_colors(): # Asset colors instance = FCMColorManager('yes') - assert isinstance(instance.get(), six.string_types) + assert isinstance(instance.get(), str) # Output: #rrggbb assert len(instance.get()) == 7 # Starts with has symbol @@ -818,7 +806,7 @@ def test_plugin_fcm_colors(): # Custom color instance = FCMColorManager('#A2B3A4') - assert isinstance(instance.get(), six.string_types) + assert isinstance(instance.get(), str) assert instance.get() == '#a2b3a4' assert bool(instance) is True # str() response does not include hashtag @@ -826,7 +814,7 @@ def test_plugin_fcm_colors(): # Custom color (no hashtag) instance = FCMColorManager('A2B3A4') - assert isinstance(instance.get(), six.string_types) + assert isinstance(instance.get(), str) # Hashtag is always part of output assert instance.get() == '#a2b3a4' assert bool(instance) is True @@ -835,7 +823,7 @@ def test_plugin_fcm_colors(): # Custom color (no hashtag) but only using 3 letter rgb values instance = FCMColorManager('AC4') - assert isinstance(instance.get(), six.string_types) + assert isinstance(instance.get(), str) # Hashtag is always part of output assert instance.get() == '#aacc44' assert bool(instance) is True diff --git a/test/test_plugin_flock.py b/test/test_plugin_flock.py index 0df3f299..655d7490 100644 --- a/test/test_plugin_flock.py +++ b/test/test_plugin_flock.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_gitter.py b/test/test_plugin_gitter.py index 5b73f2be..0d0bfcc5 100644 --- a/test/test_plugin_gitter.py +++ b/test/test_plugin_gitter.py @@ -23,15 +23,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from helpers import AppriseURLTester @@ -174,7 +167,7 @@ def test_plugin_gitter_general(mock_post, mock_get): # Variation Initializations obj = plugins.NotifyGitter(token=token, targets='apprise') assert isinstance(obj, plugins.NotifyGitter) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # apprise room was found assert obj.send(body="test") is True @@ -257,7 +250,7 @@ def test_plugin_gitter_general(mock_post, mock_get): # Variation Initializations obj = plugins.NotifyGitter(token=token, targets='apprise') assert isinstance(obj, plugins.NotifyGitter) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # apprise room was not found assert obj.send(body="test") is False diff --git a/test/test_plugin_glib.py b/test/test_plugin_glib.py index 9b5b4674..e61f180c 100644 --- a/test/test_plugin_glib.py +++ b/test/test_plugin_glib.py @@ -24,31 +24,15 @@ # THE SOFTWARE. import re -import six import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import sys import types import apprise from helpers import module_reload +from importlib import reload -try: - # Python v3.4+ - from importlib import reload -except ImportError: - try: - # Python v3.0-v3.3 - from imp import reload - except ImportError: - # Python v2.7 - pass # Disable logging for a cleaner testing output import logging @@ -122,10 +106,6 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, reload(sys.modules['apprise.plugins.NotifyDBus']) mock_mainloop.qt.DBusQtMainLoop.side_effect = None - # Python v2.x - mock_mainloop.glib.DBusGMainLoop.return_value = True - mock_mainloop.glib.DBusGMainLoop.side_effect = ImportError() - # Python 3.x mock_mainloop.glib.NativeMainLoop.return_value = True mock_mainloop.glib.NativeMainLoop.side_effect = ImportError() sys.modules['dbus.mainloop.glib'] = mock_mainloop.glib @@ -138,19 +118,19 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, # Create our instance (identify all supported types) obj = apprise.Apprise.instantiate('dbus://', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.url().startswith('dbus://_/') obj = apprise.Apprise.instantiate('kde://', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.url().startswith('kde://_/') obj = apprise.Apprise.instantiate('qt://', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.url().startswith('qt://_/') obj = apprise.Apprise.instantiate('glib://', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.url().startswith('glib://_/') obj.duration = 0 @@ -178,7 +158,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?image=True', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.url().startswith('dbus://_/') assert re.search('image=yes', obj.url()) @@ -189,7 +169,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?image=False', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.url().startswith('dbus://_/') assert re.search('image=no', obj.url()) @@ -201,7 +181,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?priority=invalid', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -209,7 +189,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?priority=high', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -217,7 +197,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?priority=2', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -226,7 +206,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?urgency=invalid', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -234,7 +214,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?urgency=high', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -242,7 +222,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?urgency=2', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -250,7 +230,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?urgency=', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -259,7 +239,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj = apprise.Apprise.instantiate( 'dbus://_/?x=5&y=5', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyDBus) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -382,7 +362,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj.duration = 0 # Test url() call - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Our notification succeeds even though the gi library was not loaded assert obj.notify( @@ -411,7 +391,7 @@ def test_plugin_dbus_general(mock_mainloop, mock_byte, mock_bytearray, obj.duration = 0 # Test url() call - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Our notification succeeds even though the gi library was not loaded assert obj.notify( diff --git a/test/test_plugin_gnome.py b/test/test_plugin_gnome.py index 79ae4401..733e5a38 100644 --- a/test/test_plugin_gnome.py +++ b/test/test_plugin_gnome.py @@ -23,18 +23,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import sys import types -import pytest +from unittest import mock + import apprise from helpers import module_reload from apprise.plugins.NotifyGnome import GnomeUrgency @@ -44,7 +36,6 @@ import logging logging.disable(logging.CRITICAL) -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") def test_plugin_gnome_general(): """ NotifyGnome() General Checks @@ -110,7 +101,7 @@ def test_plugin_gnome_general(): assert obj.enabled is True # Test url() call - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # test notifications assert obj.notify(title='title', body='body', diff --git a/test/test_plugin_gotify.py b/test/test_plugin_gotify.py index 5c68d55e..9a863c0e 100644 --- a/test/test_plugin_gotify.py +++ b/test/test_plugin_gotify.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_growl.py b/test/test_plugin_growl.py index c81488db..53698fb2 100644 --- a/test/test_plugin_growl.py +++ b/test/test_plugin_growl.py @@ -24,15 +24,8 @@ # THE SOFTWARE. import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock -import six import pytest import apprise from apprise.plugins.NotifyGrowl import GrowlPriority @@ -278,11 +271,11 @@ def test_plugin_growl_general(mock_gntp): if isinstance(obj, apprise.plugins.NotifyBase): # We loaded okay; now lets make sure we can reverse this url - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Test our privacy=True flag assert isinstance( - obj.url(privacy=True), six.string_types) is True + obj.url(privacy=True), str) is True # Instantiate the exact same object again using the URL from # the one that was already created properly diff --git a/test/test_plugin_guilded.py b/test/test_plugin_guilded.py index 1ade673d..ebbc9d36 100644 --- a/test/test_plugin_guilded.py +++ b/test/test_plugin_guilded.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests @@ -188,4 +181,4 @@ def test_plugin_guilded_general(mock_post): footer=True, thumbnail=False) # Test that we get a string response - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True diff --git a/test/test_plugin_homeassistant.py b/test/test_plugin_homeassistant.py index e0fa64ca..cfbbcbf3 100644 --- a/test/test_plugin_homeassistant.py +++ b/test/test_plugin_homeassistant.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -149,7 +142,7 @@ def test_plugin_homeassistant_general(mock_post): # Variation Initializations obj = Apprise.instantiate('hassio://localhost/accesstoken') assert isinstance(obj, plugins.NotifyHomeAssistant) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Send Notification assert obj.send(body="test") is True diff --git a/test/test_plugin_ifttt.py b/test/test_plugin_ifttt.py index c7bbe442..ce5206eb 100644 --- a/test/test_plugin_ifttt.py +++ b/test/test_plugin_ifttt.py @@ -23,13 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins diff --git a/test/test_plugin_join.py b/test/test_plugin_join.py index c0449e40..92717b72 100644 --- a/test/test_plugin_join.py +++ b/test/test_plugin_join.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_macosx.py b/test/test_plugin_macosx.py index 142864b9..b80e9607 100644 --- a/test/test_plugin_macosx.py +++ b/test/test_plugin_macosx.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock from helpers import module_reload @@ -77,7 +70,7 @@ def test_plugin_macosx_general(mock_macver, mock_system, mock_popen, tmpdir): assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True # Test url() call - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # test notifications assert obj.notify(title='title', body='body', @@ -96,7 +89,7 @@ def test_plugin_macosx_general(mock_macver, mock_system, mock_popen, tmpdir): obj = apprise.Apprise.instantiate( 'macosx://_/?image=False', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify(title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -105,7 +98,7 @@ def test_plugin_macosx_general(mock_macver, mock_system, mock_popen, tmpdir): 'macosx://_/?sound=default', suppress_exceptions=False) assert isinstance(obj, apprise.plugins.NotifyMacOSX) is True assert obj.sound == 'default' - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify(title='title', body='body', notify_type=apprise.NotifyType.INFO) is True diff --git a/test/test_plugin_mailgun.py b/test/test_plugin_mailgun.py index 2d5c4889..abc4dd18 100644 --- a/test/test_plugin_mailgun.py +++ b/test/test_plugin_mailgun.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from helpers import AppriseURLTester @@ -225,13 +218,6 @@ def test_plugin_mailgun_attachments(mock_post): body='body', title='title', notify_type=NotifyType.INFO, attach=attach) is False - # Get a appropriate "builtin" module name for pythons 2/3. - if sys.version_info.major >= 3: - builtin_open_function = 'builtins.open' - - else: - builtin_open_function = '__builtin__.open' - # Test Valid Attachment (load 3) path = ( os.path.join(TEST_VAR_DIR, 'apprise-test.gif'), @@ -243,7 +229,7 @@ def test_plugin_mailgun_attachments(mock_post): # Return our good configuration mock_post.side_effect = None mock_post.return_value = okay_response - with mock.patch(builtin_open_function, side_effect=OSError()): + with mock.patch('builtins.open', side_effect=OSError()): # We can't send the message we can't open the attachment for reading assert obj.notify( body='body', title='title', notify_type=NotifyType.INFO, @@ -251,14 +237,14 @@ def test_plugin_mailgun_attachments(mock_post): # Do it again, but fail on the third file with mock.patch( - builtin_open_function, + 'builtins.open', side_effect=(mock.Mock(), mock.Mock(), OSError())): assert obj.notify( body='body', title='title', notify_type=NotifyType.INFO, attach=attach) is False - with mock.patch(builtin_open_function) as mock_open: + with mock.patch('builtins.open') as mock_open: mock_fp = mock.Mock() mock_fp.seek.side_effect = OSError() mock_open.return_value = mock_fp diff --git a/test/test_plugin_matrix.py b/test/test_plugin_matrix.py index 4e4f2946..5637927b 100644 --- a/test/test_plugin_matrix.py +++ b/test/test_plugin_matrix.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests import pytest @@ -229,25 +222,25 @@ def test_plugin_matrix_general(mock_post, mock_get): # Variation Initializations obj = plugins.NotifyMatrix(host='host', targets='#abcd') assert isinstance(obj, plugins.NotifyMatrix) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Registration successful assert obj.send(body="test") is True obj = plugins.NotifyMatrix(host='host', user='user', targets='#abcd') assert isinstance(obj, plugins.NotifyMatrix) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Registration successful assert obj.send(body="test") is True obj = plugins.NotifyMatrix(host='host', password='passwd', targets='#abcd') assert isinstance(obj, plugins.NotifyMatrix) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # A username gets automatically generated in these cases assert obj.send(body="test") is True obj = plugins.NotifyMatrix( host='host', user='user', password='passwd', targets='#abcd') - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert isinstance(obj, plugins.NotifyMatrix) is True # Registration Successful assert obj.send(body="test") is True @@ -256,7 +249,7 @@ def test_plugin_matrix_general(mock_post, mock_get): kwargs = plugins.NotifyMatrix.parse_url( 'matrix://user:passwd@hostname/#abcd?format=html') obj = plugins.NotifyMatrix(**kwargs) - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert isinstance(obj, plugins.NotifyMatrix) is True obj.send(body="test") is True obj.send(title="title", body="test") is True @@ -264,7 +257,7 @@ def test_plugin_matrix_general(mock_post, mock_get): kwargs = plugins.NotifyMatrix.parse_url( 'matrix://user:passwd@hostname/#abcd/#abcd:localhost?format=markdown') obj = plugins.NotifyMatrix(**kwargs) - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert isinstance(obj, plugins.NotifyMatrix) is True obj.send(body="test") is True obj.send(title="title", body="test") is True @@ -272,7 +265,7 @@ def test_plugin_matrix_general(mock_post, mock_get): kwargs = plugins.NotifyMatrix.parse_url( 'matrix://user:passwd@hostname/#abcd/!abcd:localhost?format=text') obj = plugins.NotifyMatrix(**kwargs) - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert isinstance(obj, plugins.NotifyMatrix) is True obj.send(body="test") is True obj.send(title="title", body="test") is True @@ -281,7 +274,7 @@ def test_plugin_matrix_general(mock_post, mock_get): kwargs = plugins.NotifyMatrix.parse_url( 'matrix://user:passwd@hostname/#abcd?msgtype=notice') obj = plugins.NotifyMatrix(**kwargs) - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert isinstance(obj, plugins.NotifyMatrix) is True obj.send(body="test") is True obj.send(title="title", body="test") is True diff --git a/test/test_plugin_messagebird.py b/test/test_plugin_messagebird.py index db31774f..49fbc6d0 100644 --- a/test/test_plugin_messagebird.py +++ b/test/test_plugin_messagebird.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_mqtt.py b/test/test_plugin_mqtt.py index 5c8ec0e2..9d33f862 100644 --- a/test/test_plugin_mqtt.py +++ b/test/test_plugin_mqtt.py @@ -23,19 +23,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import re import sys import ssl -import six import pytest +from unittest import mock + import apprise # Disable logging for a cleaner testing output @@ -232,21 +225,12 @@ def test_plugin_mqtt_general(mock_client): assert isinstance(obj, apprise.plugins.NotifyMQTT) _mock_client.connect.return_value = None - if six.PY2: - # Python v2.7 does not support the ConnectionError exception - for side_effect in (ValueError, ssl.CertificateError): - _mock_client.connect.side_effect = side_effect - assert obj.notify(body="test=test") is False - - else: - for side_effect in ( - ValueError, - # Python 2.7 doesn't recognize ConnectionError so for that - # reason we stick a noqa entry here... - ConnectionError, # noqa: F821 - ssl.CertificateError): - _mock_client.connect.side_effect = side_effect - assert obj.notify(body="test=test") is False + for side_effect in ( + ValueError, + ConnectionError, + ssl.CertificateError): + _mock_client.connect.side_effect = side_effect + assert obj.notify(body="test=test") is False # Restore our values _mock_client.connect.side_effect = None diff --git a/test/test_plugin_msg91.py b/test/test_plugin_msg91.py index 383364bc..af3fad3d 100644 --- a/test/test_plugin_msg91.py +++ b/test/test_plugin_msg91.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_msteams.py b/test/test_plugin_msteams.py index c79045ed..b6a07cf3 100644 --- a/test/test_plugin_msteams.py +++ b/test/test_plugin_msteams.py @@ -23,14 +23,9 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock +import sys import json import requests import pytest @@ -389,6 +384,8 @@ def test_plugin_msteams_templating(mock_post, tmpdir): == 'http://localhost' +@pytest.mark.skipif( + hasattr(sys, "pypy_version_info"), reason="Does not work reliably on PyPy") @mock.patch('requests.post') def test_msteams_yaml_config(mock_post, tmpdir): """ diff --git a/test/test_plugin_nextcloud.py b/test/test_plugin_nextcloud.py index 3adf3e0f..876abb83 100644 --- a/test/test_plugin_nextcloud.py +++ b/test/test_plugin_nextcloud.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -158,7 +151,7 @@ def test_plugin_nextcloud_edge_cases(mock_post): obj = plugins.NotifyNextcloud( host="localhost", user="admin", password="pass", targets="user") assert isinstance(obj, plugins.NotifyNextcloud) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # An empty body assert obj.send(body="") is True diff --git a/test/test_plugin_nextcloudtalk.py b/test/test_plugin_nextcloudtalk.py index 048205b7..fe42cc54 100644 --- a/test/test_plugin_nextcloudtalk.py +++ b/test/test_plugin_nextcloudtalk.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -148,7 +141,7 @@ def test_plugin_nextcloudtalk_edge_cases(mock_post): obj = plugins.NotifyNextcloudTalk( host="localhost", user="admin", password="pass", targets="roomid") assert isinstance(obj, plugins.NotifyNextcloudTalk) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # An empty body assert obj.send(body="") is True diff --git a/test/test_plugin_ntfy.py b/test/test_plugin_ntfy.py index 370f5c68..be4e0873 100644 --- a/test/test_plugin_ntfy.py +++ b/test/test_plugin_ntfy.py @@ -24,13 +24,7 @@ # THE SOFTWARE. import os import json -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins diff --git a/test/test_plugin_office365.py b/test/test_plugin_office365.py index 7ddd7df2..e63bbce7 100644 --- a/test/test_plugin_office365.py +++ b/test/test_plugin_office365.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests @@ -227,7 +220,7 @@ def test_plugin_office365_general(mock_post): assert isinstance(obj, plugins.NotifyOffice365) # Test our URL generation - assert isinstance(obj.url(), six.string_types) + assert isinstance(obj.url(), str) # Test our notification assert obj.notify(title='title', body='test') is True @@ -248,7 +241,7 @@ def test_plugin_office365_general(mock_post): assert isinstance(obj, plugins.NotifyOffice365) # Test our URL generation - assert isinstance(obj.url(), six.string_types) + assert isinstance(obj.url(), str) # Test our notification assert obj.notify(title='title', body='test') is True diff --git a/test/test_plugin_opsgenie.py b/test/test_plugin_opsgenie.py index 4f77cf6b..f7b939ed 100644 --- a/test/test_plugin_opsgenie.py +++ b/test/test_plugin_opsgenie.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise.plugins.NotifyOpsgenie import OpsgeniePriority diff --git a/test/test_plugin_prowl.py b/test/test_plugin_prowl.py index 6528d630..bf6998cc 100644 --- a/test/test_plugin_prowl.py +++ b/test/test_plugin_prowl.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_pushbullet.py b/test/test_plugin_pushbullet.py index d55fab07..316190c3 100644 --- a/test/test_plugin_pushbullet.py +++ b/test/test_plugin_pushbullet.py @@ -24,13 +24,7 @@ # THE SOFTWARE. import os -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_pushed.py b/test/test_plugin_pushed.py index 6612a94e..54a5e85d 100644 --- a/test/test_plugin_pushed.py +++ b/test/test_plugin_pushed.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_pushover.py b/test/test_plugin_pushover.py index d1ca5ae8..59c8bc06 100644 --- a/test/test_plugin_pushover.py +++ b/test/test_plugin_pushover.py @@ -24,13 +24,7 @@ # THE SOFTWARE. import os -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests import pytest diff --git a/test/test_plugin_pushsafer.py b/test/test_plugin_pushsafer.py index 2c1b69ed..b8fbd1ae 100644 --- a/test/test_plugin_pushsafer.py +++ b/test/test_plugin_pushsafer.py @@ -25,13 +25,7 @@ import os import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from json import dumps @@ -262,7 +256,7 @@ def test_plugin_pushsafer_general(mock_post): attach=attach) is True # Test error reading attachment from disk - with mock.patch('io.open', side_effect=OSError): + with mock.patch('builtins.open', side_effect=OSError): obj.notify( body='body', title='title', notify_type=NotifyType.INFO, attach=attach) diff --git a/test/test_plugin_reddit.py b/test/test_plugin_reddit.py index 431032d7..cce440d3 100644 --- a/test/test_plugin_reddit.py +++ b/test/test_plugin_reddit.py @@ -23,18 +23,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six import requests -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - from apprise import plugins from helpers import AppriseURLTester +from unittest import mock from json import dumps from datetime import datetime @@ -279,7 +271,7 @@ def test_plugin_reddit_general(mock_post): # Variation Initializations obj = plugins.NotifyReddit(**kwargs) assert isinstance(obj, plugins.NotifyReddit) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Dynamically pick up on a link assert obj.send(body="http://hostname") is True diff --git a/test/test_plugin_rocket_chat.py b/test/test_plugin_rocket_chat.py index 805319c3..482d9111 100644 --- a/test/test_plugin_rocket_chat.py +++ b/test/test_plugin_rocket_chat.py @@ -22,19 +22,12 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import pytest import requests from apprise import plugins from apprise import NotifyType from helpers import AppriseURLTester +from unittest import mock # Disable logging for a cleaner testing output import logging diff --git a/test/test_plugin_sendgrid.py b/test/test_plugin_sendgrid.py index 8f457c63..854a8cef 100644 --- a/test/test_plugin_sendgrid.py +++ b/test/test_plugin_sendgrid.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_ses.py b/test/test_plugin_ses.py index 5ea00692..4bdc9073 100644 --- a/test/test_plugin_ses.py +++ b/test/test_plugin_ses.py @@ -24,13 +24,8 @@ # THE SOFTWARE. import os -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +import sys +from unittest import mock import pytest import requests @@ -43,6 +38,10 @@ from helpers import AppriseURLTester import logging logging.disable(logging.CRITICAL) +if hasattr(sys, "pypy_version_info"): + raise pytest.skip(reason="Skipping test cases which stall on PyPy", + allow_module_level=True) + # Attachment Directory TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var') diff --git a/test/test_plugin_signal.py b/test/test_plugin_signal.py index 845694c3..7c0aafac 100644 --- a/test/test_plugin_signal.py +++ b/test/test_plugin_signal.py @@ -25,13 +25,7 @@ import os import sys from json import loads -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_simplepush.py b/test/test_plugin_simplepush.py index 9c6e9a95..68a59fc8 100644 --- a/test/test_plugin_simplepush.py +++ b/test/test_plugin_simplepush.py @@ -23,13 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_sinch.py b/test/test_plugin_sinch.py index 59275a06..29986996 100644 --- a/test/test_plugin_sinch.py +++ b/test/test_plugin_sinch.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_slack.py b/test/test_plugin_slack.py index 950dfdf9..6ecee0a5 100644 --- a/test/test_plugin_slack.py +++ b/test/test_plugin_slack.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests @@ -305,7 +298,7 @@ def test_plugin_slack_oauth_access_token(mock_post): # Variation Initializations obj = plugins.NotifySlack(access_token=token, targets='#apprise') assert isinstance(obj, plugins.NotifySlack) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # apprise room was found assert obj.send(body="test") is True @@ -468,7 +461,7 @@ def test_plugin_slack_send_by_email(mock_get, mock_post): # Variation Initializations obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com') assert isinstance(obj, plugins.NotifySlack) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # No calls made yet assert mock_post.call_count == 0 @@ -524,7 +517,7 @@ def test_plugin_slack_send_by_email(mock_get, mock_post): # Variation Initializations obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com') assert isinstance(obj, plugins.NotifySlack) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # No calls made yet assert mock_post.call_count == 0 @@ -558,7 +551,7 @@ def test_plugin_slack_send_by_email(mock_get, mock_post): # Variation Initializations obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com') assert isinstance(obj, plugins.NotifySlack) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # No calls made yet assert mock_post.call_count == 0 @@ -592,7 +585,7 @@ def test_plugin_slack_send_by_email(mock_get, mock_post): # Variation Initializations obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com') assert isinstance(obj, plugins.NotifySlack) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # No calls made yet assert mock_post.call_count == 0 @@ -636,7 +629,7 @@ def test_plugin_slack_send_by_email(mock_get, mock_post): # Variation Initializations obj = plugins.NotifySlack(access_token=token, targets='user@gmail.com') assert isinstance(obj, plugins.NotifySlack) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # No calls made yet assert mock_post.call_count == 0 diff --git a/test/test_plugin_smseagle.py b/test/test_plugin_smseagle.py index c758e932..7e7acb9a 100644 --- a/test/test_plugin_smseagle.py +++ b/test/test_plugin_smseagle.py @@ -23,15 +23,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os -import sys from json import loads, dumps -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -623,14 +616,6 @@ def test_notify_smseagle_plugin_attachments(mock_post): body='body', title='title', notify_type=NotifyType.INFO, attach=path) is False - # Get a appropriate "builtin" module name for pythons 2/3. - if sys.version_info.major >= 3: - builtin_open_function = 'builtins.open' - - else: - builtin_open_function = '__builtin__.open' - - # Test Valid Attachment (load 3) path = ( os.path.join(TEST_VAR_DIR, 'apprise-test.gif'), os.path.join(TEST_VAR_DIR, 'apprise-test.gif'), @@ -641,7 +626,7 @@ def test_notify_smseagle_plugin_attachments(mock_post): # Return our good configuration mock_post.side_effect = None mock_post.return_value = okay_response - with mock.patch(builtin_open_function, side_effect=OSError()): + with mock.patch('builtins.open', side_effect=OSError()): # We can't send the message we can't open the attachment for reading assert obj.notify( body='body', title='title', notify_type=NotifyType.INFO, diff --git a/test/test_plugin_smtp2go.py b/test/test_plugin_smtp2go.py index e4287717..b4afe685 100644 --- a/test/test_plugin_smtp2go.py +++ b/test/test_plugin_smtp2go.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import sys -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from apprise import plugins @@ -204,13 +197,6 @@ def test_plugin_smtp2go_attachments(mock_post): body='body', title='title', notify_type=NotifyType.INFO, attach=attach) is False - # Get a appropriate "builtin" module name for pythons 2/3. - if sys.version_info.major >= 3: - builtin_open_function = 'builtins.open' - - else: - builtin_open_function = '__builtin__.open' - # Test Valid Attachment (load 3) path = ( os.path.join(TEST_VAR_DIR, 'apprise-test.gif'), @@ -222,7 +208,7 @@ def test_plugin_smtp2go_attachments(mock_post): # Return our good configuration mock_post.side_effect = None mock_post.return_value = okay_response - with mock.patch(builtin_open_function, side_effect=OSError()): + with mock.patch('builtins.open', side_effect=OSError()): # We can't send the message we can't open the attachment for reading assert obj.notify( body='body', title='title', notify_type=NotifyType.INFO, diff --git a/test/test_plugin_sns.py b/test/test_plugin_sns.py index 1ef4fc56..80148028 100644 --- a/test/test_plugin_sns.py +++ b/test/test_plugin_sns.py @@ -23,13 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_sparkpost.py b/test/test_plugin_sparkpost.py index 6d5c3a3f..41e8d612 100644 --- a/test/test_plugin_sparkpost.py +++ b/test/test_plugin_sparkpost.py @@ -25,13 +25,7 @@ import os import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from json import dumps diff --git a/test/test_plugin_syslog.py b/test/test_plugin_syslog.py index bdc264ce..c741424e 100644 --- a/test/test_plugin_syslog.py +++ b/test/test_plugin_syslog.py @@ -25,13 +25,7 @@ import re import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import apprise import socket diff --git a/test/test_plugin_telegram.py b/test/test_plugin_telegram.py index 666dad8f..873837aa 100644 --- a/test/test_plugin_telegram.py +++ b/test/test_plugin_telegram.py @@ -24,20 +24,12 @@ # THE SOFTWARE. import os -import six -import sys import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import requests from json import dumps from json import loads +from unittest import mock + from apprise import Apprise from apprise import AppriseAttachment from apprise import AppriseAsset @@ -289,10 +281,10 @@ def test_plugin_telegram_general(mock_post): mock_post.return_value.content = '{}' # test url call - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # test privacy version of url - assert isinstance(obj.url(privacy=True), six.string_types) is True + assert isinstance(obj.url(privacy=True), str) is True assert obj.url(privacy=True).startswith('tgram://1...p/') is True # Test that we can load the string we generate back: @@ -544,12 +536,10 @@ def test_plugin_telegram_general(mock_post): assert '-123456789525' in obj.targets -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") @mock.patch('requests.post') -def test_plugin_telegram_formating_py3(mock_post): +def test_plugin_telegram_formatting(mock_post): """ - NotifyTelegram() Python v3+ Formatting Tests - + NotifyTelegram() formatting tests """ # Disable Throttling to speed testing @@ -887,395 +877,6 @@ def test_plugin_telegram_formating_py3(mock_post): 'ok\r\n' -@pytest.mark.skipif(sys.version_info.major >= 3, reason="Requires Python 2.x+") -@mock.patch('requests.post') -def test_plugin_telegram_formating_py2(mock_post): - """ - NotifyTelegram() Python v2 Formatting Tests - - """ - - # Disable Throttling to speed testing - plugins.NotifyTelegram.request_rate_per_sec = 0 - - # Prepare Mock - mock_post.return_value = requests.Request() - mock_post.return_value.status_code = requests.codes.ok - mock_post.return_value.content = '{}' - - # Simple success response - mock_post.return_value.content = dumps({ - "ok": True, - "result": [{ - "update_id": 645421321, - "message": { - "message_id": 2, - "from": { - "id": 532389719, - "is_bot": False, - "first_name": "Chris", - "language_code": "en-US" - }, - "chat": { - "id": 532389719, - "first_name": "Chris", - "type": "private" - }, - "date": 1519694394, - "text": "/start", - "entities": [{ - "offset": 0, - "length": 6, - "type": "bot_command", - }], - }}, - ], - }) - mock_post.return_value.status_code = requests.codes.ok - - results = plugins.NotifyTelegram.parse_url( - 'tgram://123456789:abcdefg_hijklmnop/') - - instance = plugins.NotifyTelegram(**results) - assert isinstance(instance, plugins.NotifyTelegram) - - response = instance.send(title='title', body='body') - assert response is True - # 1 call to look up bot owner, and second for notification - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' - - # Reset our values - mock_post.reset_mock() - - # Now test our HTML Conversion as TEXT) - aobj = Apprise() - aobj.add('tgram://123456789:abcdefg_hijklmnop/') - assert len(aobj) == 1 - - title = '馃毃 Change detected for Apprise Test Title' - body = 'Apprise Body Title' \ - ' had a change' - - assert aobj.notify(title=title, body=body, body_format=NotifyFormat.TEXT) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a TEXT mode - assert payload['text'].encode('utf-8') == \ - '\xf0\x9f\x9a\xa8 Change detected for <i>' \ - 'Apprise Test Title</i>\r\n<' \ - 'a href="http://localhost"><i>Apprise Body Title' \ - '</i></a> had <a href="http://127.0.0.1"' \ - '>a change</a>' - - # Reset our values - mock_post.reset_mock() - - # Now test our HTML Conversion as TEXT) - aobj = Apprise() - aobj.add('tgram://123456789:abcdefg_hijklmnop/?format=html') - assert len(aobj) == 1 - - assert aobj.notify(title=title, body=body, body_format=NotifyFormat.HTML) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'].encode('utf-8') == \ - '\xf0\x9f\x9a\xa8 Change detected for Apprise Test Title' \ - '\r\nApprise Body Title ' \ - 'had a change' - - # Reset our values - mock_post.reset_mock() - - # Now test our MARKDOWN Handling - title = '# 馃毃 Change detected for _Apprise Test Title_' - body = '_[Apprise Body Title](http://localhost)_' \ - ' had [a change](http://127.0.0.1)' - - aobj = Apprise() - aobj.add('tgram://123456789:abcdefg_hijklmnop/?format=markdown') - assert len(aobj) == 1 - - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.MARKDOWN) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'].encode('utf-8') == \ - '# \xf0\x9f\x9a\xa8 Change detected for _Apprise Test Title_\r\n_' \ - '[Apprise Body Title](http://localhost)_ had ' \ - '[a change](http://127.0.0.1)' - - # Reset our values - mock_post.reset_mock() - - # Upstream to use HTML but input specified as Markdown - aobj = Apprise() - aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=html') - assert len(aobj) == 1 - - # HTML forced by the command line, but MARKDOWN specified as - # upstream mode - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.MARKDOWN) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'].encode('utf-8') == \ - '\r\n\xf0\x9f\x9a\xa8 Change detected for ' \ - 'Apprise Test Title\r\n\r\n' \ - 'Apprise Body Title' \ - ' had a change\r\n' - - # Reset our values - mock_post.reset_mock() - - # Now test hebrew types (outside of default utf-8) - # 讻讜转专转 谞驻诇讗讛 translates to 'A wonderful title' - # 讝讜 讛讜讚注讛 translates to 'This is a notification' - title = '讻讜转专转 谞驻诇讗讛' \ - .decode('utf-8').encode('ISO-8859-8') - body = '[_[讝讜 讛讜讚注讛](http://localhost)_' \ - .decode('utf-8').encode('ISO-8859-8') - - asset = AppriseAsset(encoding='utf-8') - # Now test default entries - aobj = Apprise(asset=asset) - aobj.add('tgram://123456789:abcdefg_hijklmnop/') - assert len(aobj) == 1 - - # Our notification will fail because we'll have an encoding error - assert not aobj.notify(title=title, body=body) - # Nothing was even attempted to be notified - assert mock_post.call_count == 0 - - # Let's use the expected input - asset = AppriseAsset(encoding='ISO-8859-8') - - # Now test default entries - aobj = Apprise(asset=asset) - - aobj.add('tgram://123456789:abcdefg_hijklmnop/') - assert len(aobj) == 1 - - # Our notification will work now - assert aobj.notify(title=title, body=body) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot123456789:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'].encode('utf-8') == \ - '\xd7\x9b\xd7\x95\xd7\xaa\xd7\xa8\xd7\xaa '\ - '\xd7\xa0\xd7\xa4\xd7\x9c\xd7\x90\xd7\x94\r\n[_[\xd7\x96\xd7\x95 '\ - '\xd7\x94\xd7\x95\xd7\x93\xd7\xa2\xd7\x94](http://localhost)_' - - # Now we'll test an edge case where a title was defined, but after - # processing it, it was determiend there really wasn't anything there - # at all at the end of the day. - - # Reset our values - mock_post.reset_mock() - - # Upstream to use HTML but input specified as Markdown - aobj = Apprise() - aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=markdown') - assert len(aobj) == 1 - - # Now test our MARKDOWN Handling (no title defined... not really anyway) - title = '# ' - body = '_[Apprise Body Title](http://localhost)_' \ - ' had [a change](http://127.0.0.2)' - - # MARKDOWN forced by the command line, but TEXT specified as - # upstream mode - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.TEXT) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'] == \ - '_[Apprise Body Title](http://localhost)_ had ' \ - '[a change](http://127.0.0.2)' - - # Reset our values - mock_post.reset_mock() - - # Upstream to use HTML but input specified as Markdown - aobj = Apprise() - aobj.add('tgram://987654321:abcdefg_hijklmnop/?format=markdown') - assert len(aobj) == 1 - - # Set an actual title this time - title = '# A Great Title' - body = '_[Apprise Body Title](http://localhost)_' \ - ' had [a change](http://127.0.0.2)' - - # MARKDOWN forced by the command line, but TEXT specified as - # upstream mode - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.TEXT) - - # Test our calls - assert mock_post.call_count == 2 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/getUpdates' - assert mock_post.call_args_list[1][0][0] == \ - 'https://api.telegram.org/bot987654321:abcdefg_hijklmnop/sendMessage' - - payload = loads(mock_post.call_args_list[1][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'] == \ - '# A Great Title\r\n_[Apprise Body Title](http://localhost)_ had ' \ - '[a change](http://127.0.0.2)' - - # Reset our values - mock_post.reset_mock() - - # - # Now test that
is correctly escaped - # - title = 'Test Message Title' - body = 'Test Message Body
ok
' - - aobj = Apprise() - aobj.add('tgram://1234:aaaaaaaaa/-1123456245134') - assert len(aobj) == 1 - - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.MARKDOWN) - - # Test our calls - assert mock_post.call_count == 1 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot1234:aaaaaaaaa/sendMessage' - - payload = loads(mock_post.call_args_list[0][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'] == \ - 'Test Message Title\r\n' \ - '\r\n' \ - 'Test Message Body\r\n' \ - 'ok\r\n' - - # Reset our values - mock_post.reset_mock() - - # - # Now test that
is correctly escaped as it would have been via the - # CLI mode where the body_format is TEXT - # - - aobj = Apprise() - aobj.add('tgram://1234:aaaaaaaaa/-1123456245134') - assert len(aobj) == 1 - - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.TEXT) - - # Test our calls - assert mock_post.call_count == 1 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot1234:aaaaaaaaa/sendMessage' - - payload = loads(mock_post.call_args_list[0][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'] == \ - 'Test Message Title\r\n' \ - 'Test Message Body <br/> ok</br>' - - # Reset our values - mock_post.reset_mock() - - # - # Now test that
is correctly escaped if fed as HTML - # - - aobj = Apprise() - aobj.add('tgram://1234:aaaaaaaaa/-1123456245134') - assert len(aobj) == 1 - - assert aobj.notify( - title=title, body=body, body_format=NotifyFormat.HTML) - - # Test our calls - assert mock_post.call_count == 1 - - assert mock_post.call_args_list[0][0][0] == \ - 'https://api.telegram.org/bot1234:aaaaaaaaa/sendMessage' - - payload = loads(mock_post.call_args_list[0][1]['data']) - - # Test that everything is escaped properly in a HTML mode - assert payload['text'] == \ - 'Test Message Title\r\n' \ - 'Test Message Body\r\n' \ - 'ok\r\n' - - @mock.patch('requests.post') def test_plugin_telegram_html_formatting(mock_post): """ diff --git a/test/test_plugin_twilio.py b/test/test_plugin_twilio.py index 07d79058..77df8971 100644 --- a/test/test_plugin_twilio.py +++ b/test/test_plugin_twilio.py @@ -23,14 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests import pytest @@ -165,7 +158,7 @@ def test_plugin_twilio_auth(mock_post): 'twilio://{}:{}@{}/{}' .format(account_sid, auth_token, source, dest)) assert isinstance(obj, plugins.NotifyTwilio) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Send Notification assert obj.send(body=message_contents) is True @@ -175,7 +168,7 @@ def test_plugin_twilio_auth(mock_post): 'twilio://{}:{}@{}/{}?apikey={}' .format(account_sid, auth_token, source, dest, apikey)) assert isinstance(obj, plugins.NotifyTwilio) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Send Notification assert obj.send(body=message_contents) is True diff --git a/test/test_plugin_twist.py b/test/test_plugin_twist.py index 1dbd5e12..ae428bcf 100644 --- a/test/test_plugin_twist.py +++ b/test/test_plugin_twist.py @@ -23,13 +23,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import requests from json import dumps @@ -271,21 +265,6 @@ def test_plugin_twist_auth(mock_post, mock_get): # Calling logout on an object already logged out obj.logout() - # Force a token (to imply we've logged in) - obj.token = 'abc' - - mock_post.return_value.status_code = requests.codes.ok - mock_get.return_value.status_code = requests.codes.ok - - # Test Python v3.5 LookupError Bug: https://bugs.python.org/issue29288 - mock_post.side_effect = LookupError() - mock_get.side_effect = LookupError() - obj.access_token = 'abc' - obj.user_id = '123' - - # Tidy object - del obj - @mock.patch('requests.get') @mock.patch('requests.post') diff --git a/test/test_plugin_twitter.py b/test/test_plugin_twitter.py index 9c50c3cc..afd94a80 100644 --- a/test/test_plugin_twitter.py +++ b/test/test_plugin_twitter.py @@ -24,14 +24,7 @@ # THE SOFTWARE. import os -import six -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests @@ -262,7 +255,7 @@ def test_plugin_twitter_general(mock_post, mock_get): targets=screen_name) assert isinstance(obj, plugins.NotifyTwitter) is True - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # apprise room was found assert obj.send(body="test") is True diff --git a/test/test_plugin_vonage.py b/test/test_plugin_vonage.py index 3606ec09..84a64cc0 100644 --- a/test/test_plugin_vonage.py +++ b/test/test_plugin_vonage.py @@ -22,13 +22,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock +from unittest import mock import pytest import requests diff --git a/test/test_plugin_windows.py b/test/test_plugin_windows.py index f9377716..9e4657de 100644 --- a/test/test_plugin_windows.py +++ b/test/test_plugin_windows.py @@ -23,37 +23,19 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import pytest -try: - # Python 3.x - from unittest import mock - -except ImportError: - # Python 2.7 - import mock - import sys -import six import types -import apprise +import pytest +from importlib import reload +from unittest import mock -try: - # Python v3.4+ - from importlib import reload -except ImportError: - try: - # Python v3.0-v3.3 - from imp import reload - except ImportError: - # Python v2.7 - pass +import apprise # Disable logging for a cleaner testing output import logging logging.disable(logging.CRITICAL) -@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+") @pytest.mark.skipif(( 'win32api' in sys.modules or 'win32con' in sys.modules or @@ -124,7 +106,7 @@ def test_plugin_windows_mocked(): obj.duration = 0 # Test URL functionality - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Check that it found our mocked environments assert obj.enabled is True @@ -140,7 +122,7 @@ def test_plugin_windows_mocked(): obj = apprise.Apprise.instantiate( 'windows://_/?image=True', suppress_exceptions=False) obj.duration = 0 - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -148,14 +130,14 @@ def test_plugin_windows_mocked(): obj = apprise.Apprise.instantiate( 'windows://_/?image=False', suppress_exceptions=False) obj.duration = 0 - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True obj = apprise.Apprise.instantiate( 'windows://_/?duration=1', suppress_exceptions=False) - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # loads okay assert obj.duration == 1 assert obj.notify( @@ -225,7 +207,7 @@ def test_plugin_windows_native( obj.duration = 0 # Test URL functionality - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True # Check that it found our mocked environments assert obj.enabled is True @@ -241,7 +223,7 @@ def test_plugin_windows_native( obj = apprise.Apprise.instantiate( 'windows://_/?image=True', suppress_exceptions=False) obj.duration = 0 - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True @@ -249,14 +231,14 @@ def test_plugin_windows_native( obj = apprise.Apprise.instantiate( 'windows://_/?image=False', suppress_exceptions=False) obj.duration = 0 - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True obj = apprise.Apprise.instantiate( 'windows://_/?duration=1', suppress_exceptions=False) - assert isinstance(obj.url(), six.string_types) is True + assert isinstance(obj.url(), str) is True assert obj.notify( title='title', body='body', notify_type=apprise.NotifyType.INFO) is True diff --git a/test/test_rest_plugins.py b/test/test_rest_plugins.py index c709c2dc..47bde65b 100644 --- a/test/test_rest_plugins.py +++ b/test/test_rest_plugins.py @@ -307,8 +307,7 @@ def test_notify_overflow_split(): # Test title max length title_maxlen = title_len - # Enforce a body length - # Wrap in int() so Python v3 doesn't convert the response into a float + # Enforce a body length. Make sure it's an int. body_maxlen = int(body_len / 4) def __init__(self, *args, **kwargs): @@ -345,8 +344,7 @@ def test_notify_overflow_split(): # Enforce no title title_maxlen = 0 - # Enforce a body length based on the title - # Wrap in int() so Python v3 doesn't convert the response into a float + # Enforce a body length based on the title. Make sure it's an int. body_maxlen = int(title_len / 4) def __init__(self, *args, **kwargs): diff --git a/tox.ini b/tox.ini index a3652a98..01d9cea3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py36,py37,py38,py39,py39-dev,pypy,pypy3,bare,coverage-report +envlist = py36,py37,py38,py39,py310,pypy36,pypy37,pypy38,pypy39,bare,coverage-report [testenv] @@ -58,6 +58,17 @@ commands = coverage run --parallel -m pytest {posargs} flake8 . --count --show-source --statistics +[testenv:py310] +deps= + dbus-python + -r{toxinidir}/requirements.txt + -r{toxinidir}/dev-requirements.txt + -r{toxinidir}/all-plugin-requirements.txt +commands = + python setup.py compile_catalog + coverage run --parallel -m pytest {posargs} + flake8 . --count --show-source --statistics + [testenv:bare] deps= @@ -68,9 +79,10 @@ commands = coverage run --parallel -m pytest {posargs} flake8 . --count --show-source --statistics -[testenv:py39-dev] +[testenv:pypy36] deps= - dbus-python + # cryptography 3.3 is the last one not needing a Rust toolchain. + cryptography<3.4 -r{toxinidir}/requirements.txt -r{toxinidir}/dev-requirements.txt -r{toxinidir}/all-plugin-requirements.txt @@ -79,8 +91,21 @@ commands = coverage run --parallel -m pytest {posargs} flake8 . --count --show-source --statistics -[testenv:pypy] +[testenv:pypy37] +deps= + # cryptography 3.3 is the last one not needing a Rust toolchain. + cryptography<3.4 + -r{toxinidir}/requirements.txt + -r{toxinidir}/dev-requirements.txt + -r{toxinidir}/all-plugin-requirements.txt +commands = + python setup.py compile_catalog + coverage run --parallel -m pytest {posargs} + +[testenv:pypy38] deps= + # cryptography 3.3 is the last one not needing a Rust toolchain. + cryptography<3.4 -r{toxinidir}/requirements.txt -r{toxinidir}/dev-requirements.txt -r{toxinidir}/all-plugin-requirements.txt @@ -89,15 +114,14 @@ commands = coverage run --parallel -m pytest {posargs} flake8 . --count --show-source --statistics -[testenv:pypy3] +[testenv:pypy39] deps= + # cryptography 3.3 is the last one not needing a Rust toolchain. + cryptography<3.4 -r{toxinidir}/requirements.txt -r{toxinidir}/dev-requirements.txt -r{toxinidir}/all-plugin-requirements.txt -# Last supported cryptography version that can link against -# OpenSSL v1.0.2 (which pypy35 uses) is 3.1.1 commands = - pip install --upgrade cryptography==3.1.1 python setup.py compile_catalog coverage run --parallel -m pytest {posargs} flake8 . --count --show-source --statistics