mirror of
https://github.com/caronc/apprise.git
synced 2024-11-24 17:14:00 +01:00
Added support for recent CPython and PyPy versions; Droped Python v2.7 Support (#680)
This commit is contained in:
parent
f7244cce3d
commit
00afe4e5b6
9
.gitignore
vendored
9
.gitignore
vendored
@ -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
|
||||
|
41
.travis.yml
41
.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:
|
||||
|
@ -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
|
@ -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 title and isinstance(title, bytes):
|
||||
title = title.decode(self.asset.encoding)
|
||||
|
||||
if body and isinstance(body, str): # noqa: F821
|
||||
body = body.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
|
||||
|
@ -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: ...
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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: ...
|
@ -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
|
||||
|
||||
|
@ -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: ...
|
@ -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
|
||||
|
||||
except ImportError:
|
||||
# Python v3.4+
|
||||
import builtins
|
||||
builtins.__dict__['_'] = lambda x: x # pragma: no branch
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -34,4 +34,3 @@ class AttachBase:
|
||||
) -> Dict[str, Any]: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __bool__(self) -> bool: ...
|
||||
def __nonzero__(self) -> bool: ...
|
@ -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 = {}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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 = {}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.:
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
))]
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -58,7 +58,7 @@ from ..utils import parse_list
|
||||
from ..utils import parse_bool
|
||||
|
||||
|
||||
class DapnetPriority(object):
|
||||
class DapnetPriority:
|
||||
NORMAL = 0
|
||||
EMERGENCY = 1
|
||||
|
||||
|
@ -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(
|
||||
# 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]
|
||||
|
||||
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 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]
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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 = {}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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 json.decoder import JSONDecodeError
|
||||
from urllib.parse import urlencode as _urlencode
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
# Python 3.x
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
except ImportError:
|
||||
# Python v2.7 Backwards Compatibility support
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
|
||||
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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -60,7 +60,7 @@ except (ImportError, ValueError, AttributeError):
|
||||
|
||||
|
||||
# Urgencies
|
||||
class GnomeUrgency(object):
|
||||
class GnomeUrgency:
|
||||
LOW = 0
|
||||
NORMAL = 1
|
||||
HIGH = 2
|
||||
|
@ -41,7 +41,7 @@ from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
# Priorities
|
||||
class GotifyPriority(object):
|
||||
class GotifyPriority:
|
||||
LOW = 0
|
||||
MODERATE = 3
|
||||
NORMAL = 5
|
||||
|
@ -46,7 +46,7 @@ except ImportError:
|
||||
|
||||
|
||||
# Priorities
|
||||
class GrowlPriority(object):
|
||||
class GrowlPriority:
|
||||
LOW = -2
|
||||
MODERATE = -1
|
||||
NORMAL = 0
|
||||
|
@ -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)
|
||||
|
@ -63,7 +63,7 @@ JOIN_IMAGE_XY = NotifyImageSize.XY_72
|
||||
|
||||
|
||||
# Priorities
|
||||
class JoinPriority(object):
|
||||
class JoinPriority:
|
||||
LOW = -2
|
||||
MODERATE = -1
|
||||
NORMAL = 0
|
||||
|
@ -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<value>.+?)\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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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<home_server>[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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 = {}
|
||||
|
@ -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 = {
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
|
||||
|
@ -32,7 +32,7 @@ from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
# Priorities
|
||||
class ProwlPriority(object):
|
||||
class ProwlPriority:
|
||||
LOW = -2
|
||||
MODERATE = -1
|
||||
NORMAL = 0
|
||||
|
@ -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 = (
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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.' \
|
||||
|
@ -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 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]
|
||||
|
||||
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")
|
||||
|
@ -47,7 +47,7 @@ CONTACT_REGEX = re.compile(
|
||||
|
||||
|
||||
# Priorities
|
||||
class SMSEaglePriority(object):
|
||||
class SMSEaglePriority:
|
||||
NORMAL = 0
|
||||
HIGH = 1
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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<user>[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
|
||||
|
@ -24,7 +24,6 @@
|
||||
# THE SOFTWARE.
|
||||
|
||||
import re
|
||||
import six
|
||||
import requests
|
||||
import base64
|
||||
|
||||
@ -157,11 +156,11 @@ class NotifyXML(NotifyBase):
|
||||
</soapenv:Envelope>"""
|
||||
|
||||
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)
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
149
apprise/utils.py
149
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))
|
||||
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()
|
||||
|
||||
return None
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
try:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
|
||||
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
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
import importlib.util
|
||||
except Exception as e:
|
||||
# module isn't loadable
|
||||
del sys.modules[name]
|
||||
module = None
|
||||
|
||||
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()
|
||||
logger.debug(
|
||||
'Custom module exception raised from %s (name=%s) %s',
|
||||
path, name, str(e))
|
||||
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
try:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
return module
|
||||
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
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))
|
||||
|
||||
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<name>[_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)):
|
||||
|
10
bin/apprise
10
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__":
|
||||
|
@ -1,6 +1,5 @@
|
||||
coverage
|
||||
flake8
|
||||
mock; python_version=='2.7'
|
||||
pytest
|
||||
pytest-cov
|
||||
tox
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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 <lead2gold@gmail.com> - 1.0.0-3
|
||||
- Python 2 Support dropped
|
||||
|
||||
* Wed Aug 31 2022 Chris Caron <lead2gold@gmail.com> - 1.0.0-2
|
||||
- Rebuilt for RHEL9 Support
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
requests
|
||||
requests-oauthlib
|
||||
six
|
||||
click >= 5.0
|
||||
markdown
|
||||
PyYAML
|
||||
|
@ -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
|
||||
|
18
setup.py
18
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', ],
|
||||
)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
else:
|
||||
# Python >= v3.x
|
||||
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__)
|
||||
spec = inspect.getfullargspec(
|
||||
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__)
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user