mirror of https://github.com/caronc/apprise
Added support for recent CPython and PyPy versions; Droped Python v2.7 Support (#680)
parent
f7244cce3d
commit
00afe4e5b6
|
@ -26,15 +26,6 @@ sdist/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.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
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
41
.travis.yml
41
.travis.yml
|
@ -1,6 +1,6 @@
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
dist: xenial
|
dist: focal
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
|
@ -11,19 +11,23 @@ matrix:
|
||||||
include:
|
include:
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
env: TOXENV=py36
|
env: TOXENV=py36
|
||||||
- python: "3.7.7"
|
- python: "3.7"
|
||||||
env: TOXENV=py37
|
env: TOXENV=py37
|
||||||
- python: "3.8"
|
- python: "3.8"
|
||||||
env: TOXENV=py38
|
env: TOXENV=py38
|
||||||
- python: "3.9"
|
- python: "3.9"
|
||||||
env: TOXENV=py39
|
env: TOXENV=py39
|
||||||
- python: "3.9-dev"
|
- python: "3.10"
|
||||||
env: TOXENV=py39-dev
|
env: TOXENV=py310
|
||||||
# PyPy Environments
|
# PyPy Environments
|
||||||
- python: "pypy2.7-6.0"
|
- python: "pypy3.6-7.3.3"
|
||||||
env: TOXENV=pypy
|
env: TOXENV=pypy36
|
||||||
- python: "pypy3.5-7.0"
|
- python: "pypy3.7-7.3.9"
|
||||||
env: TOXENV=pypy3
|
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
|
# An extra environment where additional packages are not installed
|
||||||
- python: "3.9"
|
- python: "3.9"
|
||||||
env:
|
env:
|
||||||
|
@ -31,20 +35,27 @@ matrix:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install babel
|
- 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
|
# Use up-to-date versions of tox, pip, virtualenv, and wheel.
|
||||||
- pip install -U tox pip virtualenv
|
- 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 codecov
|
||||||
- pip install -r dev-requirements.txt
|
- pip install -r dev-requirements.txt
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
|
|
||||||
# bare installs do not include extra package dependencies
|
# bare installs do not include extra package dependencies
|
||||||
- if [[ $TOXENV != 'bare' ]]; then pip install -r all-plugin-requirements.txt; fi
|
- if [[ $TOXENV != 'bare' ]]; then pip install -r all-plugin-requirements.txt; fi
|
||||||
# pypy and bare installs do not include dbus-python
|
# pypy and bare installs do not include dbus-python
|
||||||
- if [[ $TOXENV != 'bare' ]] && [[ $TRAVIS_PYTHON_VERSION != 'pypy'* ]]; then travis_retry pip install dbus-python; fi
|
- 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
|
# Fix/workaround: Python 3.7 importlib-metadata becomes incompatible with flake8,
|
||||||
# the only way around the Travis CI Builder issues
|
# unless we use a version that still supports EntryPoints.get().
|
||||||
- if [[ $TOXENV == 'py37' ]]; then pip uninstall --yes flake8; fi
|
# `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
|
# run tests
|
||||||
script:
|
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.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from . import common
|
from . import common
|
||||||
from .conversion import convert_between
|
from .conversion import convert_between
|
||||||
|
@ -44,13 +43,13 @@ from .plugins.NotifyBase import NotifyBase
|
||||||
from . import plugins
|
from . import plugins
|
||||||
from . import __version__
|
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
|
# compatible with Python v2
|
||||||
|
# TODO: Review after dropping support for Python 2.
|
||||||
from . import py3compat
|
from . import py3compat
|
||||||
ASYNCIO_SUPPORT = not six.PY2
|
|
||||||
|
|
||||||
|
|
||||||
class Apprise(object):
|
class Apprise:
|
||||||
"""
|
"""
|
||||||
Our Notification Manager
|
Our Notification Manager
|
||||||
|
|
||||||
|
@ -124,7 +123,7 @@ class Apprise(object):
|
||||||
# Prepare our Asset Object
|
# Prepare our Asset Object
|
||||||
asset = asset if isinstance(asset, AppriseAsset) else AppriseAsset()
|
asset = asset if isinstance(asset, AppriseAsset) else AppriseAsset()
|
||||||
|
|
||||||
if isinstance(url, six.string_types):
|
if isinstance(url, str):
|
||||||
# Acquire our url tokens
|
# Acquire our url tokens
|
||||||
results = plugins.url_to_dict(
|
results = plugins.url_to_dict(
|
||||||
url, secure_logging=asset.secure_logging)
|
url, secure_logging=asset.secure_logging)
|
||||||
|
@ -247,7 +246,7 @@ class Apprise(object):
|
||||||
# prepare default asset
|
# prepare default asset
|
||||||
asset = self.asset
|
asset = self.asset
|
||||||
|
|
||||||
if isinstance(servers, six.string_types):
|
if isinstance(servers, str):
|
||||||
# build our server list
|
# build our server list
|
||||||
servers = parse_urls(servers)
|
servers = parse_urls(servers)
|
||||||
if len(servers) == 0:
|
if len(servers) == 0:
|
||||||
|
@ -275,7 +274,7 @@ class Apprise(object):
|
||||||
self.servers.append(_server)
|
self.servers.append(_server)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif not isinstance(_server, (six.string_types, dict)):
|
elif not isinstance(_server, (str, dict)):
|
||||||
logger.error(
|
logger.error(
|
||||||
"An invalid notification (type={}) was specified.".format(
|
"An invalid notification (type={}) was specified.".format(
|
||||||
type(_server)))
|
type(_server)))
|
||||||
|
@ -306,7 +305,7 @@ class Apprise(object):
|
||||||
|
|
||||||
def find(self, tag=common.MATCH_ALL_TAG, match_always=True):
|
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,
|
body_format=None, tag=common.MATCH_ALL_TAG, match_always=True,
|
||||||
attach=None, interpret_escapes=None):
|
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
|
If the body_format specified is NotifyFormat.MARKDOWN, it will
|
||||||
be converted to HTML if the Notification type expects this.
|
be converted to HTML if the Notification type expects this.
|
||||||
|
|
||||||
if the tag is specified (either a string or a set/list/tuple
|
if the tag is specified (either a string or a set/list/tuple
|
||||||
of strings), then only the notifications flagged with that
|
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)
|
are notified (tag=MATCH_ALL_TAG)
|
||||||
|
|
||||||
This function returns True if all notifications were successfully
|
This function returns True if all notifications were successfully
|
||||||
|
@ -363,14 +362,13 @@ class Apprise(object):
|
||||||
simply having empty configuration files that were read.
|
simply having empty configuration files that were read.
|
||||||
|
|
||||||
Attach can contain a list of attachment URLs. attach can also be
|
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
|
identifies the products you wish to notify
|
||||||
|
|
||||||
Set interpret_escapes to True if you want to pre-escape a string
|
Set interpret_escapes to True if you want to pre-escape a string
|
||||||
such as turning a \n into an actual new line, etc.
|
such as turning a \n into an actual new line, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ASYNCIO_SUPPORT:
|
|
||||||
return py3compat.asyncio.tosync(
|
return py3compat.asyncio.tosync(
|
||||||
self.async_notify(
|
self.async_notify(
|
||||||
body, title,
|
body, title,
|
||||||
|
@ -381,42 +379,16 @@ class Apprise(object):
|
||||||
debug=self.debug
|
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
|
|
||||||
|
|
||||||
def async_notify(self, *args, **kwargs):
|
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
|
asynchronous callers. This method is an async method that should be
|
||||||
awaited on, even if it is missing the async keyword in its signature.
|
awaited on, even if it is missing the async keyword in its signature.
|
||||||
(This is omitted to preserve syntax compatibility with Python 2.)
|
(This is omitted to preserve syntax compatibility with Python 2.)
|
||||||
|
|
||||||
The arguments are identical to those of Apprise.notify(). This method
|
The arguments are identical to those of Apprise.notify().
|
||||||
is not available in Python 2.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
coroutines = list(
|
coroutines = list(
|
||||||
self._notifyall(
|
self._notifyall(
|
||||||
|
@ -477,7 +449,7 @@ class Apprise(object):
|
||||||
tag=common.MATCH_ALL_TAG, match_always=True, attach=None,
|
tag=common.MATCH_ALL_TAG, match_always=True, attach=None,
|
||||||
interpret_escapes=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
|
Returns a generator that calls handler for each notification. The first
|
||||||
and only argument supplied to handler is the server, and the keyword
|
and only argument supplied to handler is the server, and the keyword
|
||||||
|
@ -496,22 +468,10 @@ class Apprise(object):
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if six.PY2:
|
if title and isinstance(title, bytes):
|
||||||
# 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)
|
title = title.decode(self.asset.encoding)
|
||||||
|
|
||||||
if body and isinstance(body, str): # noqa: F821
|
if body and isinstance(body, bytes):
|
||||||
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)
|
body = body.decode(self.asset.encoding)
|
||||||
|
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
|
@ -580,43 +540,12 @@ class Apprise(object):
|
||||||
.encode('ascii', 'backslashreplace')\
|
.encode('ascii', 'backslashreplace')\
|
||||||
.decode('unicode-escape')
|
.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:
|
except AttributeError:
|
||||||
# Must be of string type
|
# Must be of string type
|
||||||
msg = 'Failed to escape message body'
|
msg = 'Failed to escape message body'
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise TypeError(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(
|
yield handler(
|
||||||
server,
|
server,
|
||||||
body=conversion_body_map[server.notify_format],
|
body=conversion_body_map[server.notify_format],
|
||||||
|
@ -669,12 +598,12 @@ class Apprise(object):
|
||||||
|
|
||||||
# Standard protocol(s) should be None or a tuple
|
# Standard protocol(s) should be None or a tuple
|
||||||
protocols = getattr(plugin, 'protocol', None)
|
protocols = getattr(plugin, 'protocol', None)
|
||||||
if isinstance(protocols, six.string_types):
|
if isinstance(protocols, str):
|
||||||
protocols = (protocols, )
|
protocols = (protocols, )
|
||||||
|
|
||||||
# Secure protocol(s) should be None or a tuple
|
# Secure protocol(s) should be None or a tuple
|
||||||
secure_protocols = getattr(plugin, 'secure_protocol', None)
|
secure_protocols = getattr(plugin, 'secure_protocol', None)
|
||||||
if isinstance(secure_protocols, six.string_types):
|
if isinstance(secure_protocols, str):
|
||||||
secure_protocols = (secure_protocols, )
|
secure_protocols = (secure_protocols, )
|
||||||
|
|
||||||
# Add our protocol details to our content
|
# Add our protocol details to our content
|
||||||
|
@ -779,15 +708,8 @@ class Apprise(object):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows the Apprise object to be wrapped in an Python 3.x based 'if
|
Allows the Apprise object to be wrapped in an 'if statement'.
|
||||||
statement'. True is returned if at least one service has been loaded.
|
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.
|
|
||||||
"""
|
"""
|
||||||
return len(self) > 0
|
return len(self) > 0
|
||||||
|
|
||||||
|
@ -807,7 +729,3 @@ class Apprise(object):
|
||||||
"""
|
"""
|
||||||
return sum([1 if not isinstance(s, (ConfigBase, AppriseConfig))
|
return sum([1 if not isinstance(s, (ConfigBase, AppriseConfig))
|
||||||
else len(s.servers()) for s in self.servers])
|
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 pop(self, index: int) -> ConfigBase: ...
|
||||||
def __getitem__(self, index: int) -> ConfigBase: ...
|
def __getitem__(self, index: int) -> ConfigBase: ...
|
||||||
def __bool__(self) -> bool: ...
|
def __bool__(self) -> bool: ...
|
||||||
def __nonzero__(self) -> bool: ...
|
|
||||||
def __iter__(self) -> Iterator[ConfigBase]: ...
|
def __iter__(self) -> Iterator[ConfigBase]: ...
|
||||||
def __len__(self) -> int: ...
|
def __len__(self) -> int: ...
|
|
@ -33,7 +33,7 @@ from .common import NotifyType
|
||||||
from .utils import module_detection
|
from .utils import module_detection
|
||||||
|
|
||||||
|
|
||||||
class AppriseAsset(object):
|
class AppriseAsset:
|
||||||
"""
|
"""
|
||||||
Provides a supplimentary class that can be used to provide extra
|
Provides a supplimentary class that can be used to provide extra
|
||||||
information and details that can be used by Apprise such as providing
|
information and details that can be used by Apprise such as providing
|
||||||
|
@ -107,8 +107,8 @@ class AppriseAsset(object):
|
||||||
# - NotifyFormat.HTML
|
# - NotifyFormat.HTML
|
||||||
# - None
|
# - None
|
||||||
#
|
#
|
||||||
# If no format is specified (hence None), then no special pre-formating
|
# If no format is specified (hence None), then no special pre-formatting
|
||||||
# actions will take place during a notificaton. This has been and always
|
# actions will take place during a notification. This has been and always
|
||||||
# will be the default.
|
# will be the default.
|
||||||
body_format = None
|
body_format = None
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from . import attachment
|
from . import attachment
|
||||||
from . import URLBase
|
from . import URLBase
|
||||||
from .AppriseAsset import AppriseAsset
|
from .AppriseAsset import AppriseAsset
|
||||||
|
@ -35,7 +33,7 @@ from .common import ATTACHMENT_SCHEMA_MAP
|
||||||
from .utils import GET_SCHEMA_RE
|
from .utils import GET_SCHEMA_RE
|
||||||
|
|
||||||
|
|
||||||
class AppriseAttachment(object):
|
class AppriseAttachment:
|
||||||
"""
|
"""
|
||||||
Our Apprise Attachment File Manager
|
Our Apprise Attachment File Manager
|
||||||
|
|
||||||
|
@ -143,7 +141,7 @@ class AppriseAttachment(object):
|
||||||
self.attachments.append(attachments)
|
self.attachments.append(attachments)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif isinstance(attachments, six.string_types):
|
elif isinstance(attachments, str):
|
||||||
# Save our path
|
# Save our path
|
||||||
attachments = (attachments, )
|
attachments = (attachments, )
|
||||||
|
|
||||||
|
@ -162,7 +160,7 @@ class AppriseAttachment(object):
|
||||||
return_status = False
|
return_status = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(_attachment, six.string_types):
|
if isinstance(_attachment, str):
|
||||||
logger.debug("Loading attachment: {}".format(_attachment))
|
logger.debug("Loading attachment: {}".format(_attachment))
|
||||||
# Instantiate ourselves an object, this function throws or
|
# Instantiate ourselves an object, this function throws or
|
||||||
# returns None if it fails
|
# returns None if it fails
|
||||||
|
@ -296,15 +294,8 @@ class AppriseAttachment(object):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows the Apprise object to be wrapped in an Python 3.x based 'if
|
Allows the Apprise object to be wrapped in an 'if statement'.
|
||||||
statement'. True is returned if at least one service has been loaded.
|
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.
|
|
||||||
"""
|
"""
|
||||||
return True if self.attachments else False
|
return True if self.attachments else False
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,5 @@ class AppriseAttachment:
|
||||||
def pop(self, index: int = ...) -> AttachBase: ...
|
def pop(self, index: int = ...) -> AttachBase: ...
|
||||||
def __getitem__(self, index: int) -> AttachBase: ...
|
def __getitem__(self, index: int) -> AttachBase: ...
|
||||||
def __bool__(self) -> bool: ...
|
def __bool__(self) -> bool: ...
|
||||||
def __nonzero__(self) -> bool: ...
|
|
||||||
def __iter__(self) -> Iterator[AttachBase]: ...
|
def __iter__(self) -> Iterator[AttachBase]: ...
|
||||||
def __len__(self) -> int: ...
|
def __len__(self) -> int: ...
|
|
@ -23,8 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
from . import ConfigBase
|
from . import ConfigBase
|
||||||
from . import CONFIG_FORMATS
|
from . import CONFIG_FORMATS
|
||||||
|
@ -37,7 +35,7 @@ from .utils import is_exclusive_match
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
|
|
||||||
|
|
||||||
class AppriseConfig(object):
|
class AppriseConfig:
|
||||||
"""
|
"""
|
||||||
Our Apprise Configuration File Manager
|
Our Apprise Configuration File Manager
|
||||||
|
|
||||||
|
@ -169,7 +167,7 @@ class AppriseConfig(object):
|
||||||
self.configs.append(configs)
|
self.configs.append(configs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
elif isinstance(configs, six.string_types):
|
elif isinstance(configs, str):
|
||||||
# Save our path
|
# Save our path
|
||||||
configs = (configs, )
|
configs = (configs, )
|
||||||
|
|
||||||
|
@ -187,7 +185,7 @@ class AppriseConfig(object):
|
||||||
self.configs.append(_config)
|
self.configs.append(_config)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif not isinstance(_config, six.string_types):
|
elif not isinstance(_config, str):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"An invalid configuration (type={}) was specified.".format(
|
"An invalid configuration (type={}) was specified.".format(
|
||||||
type(_config)))
|
type(_config)))
|
||||||
|
@ -241,7 +239,7 @@ class AppriseConfig(object):
|
||||||
# prepare default asset
|
# prepare default asset
|
||||||
asset = self.asset
|
asset = self.asset
|
||||||
|
|
||||||
if not isinstance(content, six.string_types):
|
if not isinstance(content, str):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"An invalid configuration (type={}) was specified.".format(
|
"An invalid configuration (type={}) was specified.".format(
|
||||||
type(content)))
|
type(content)))
|
||||||
|
@ -432,15 +430,8 @@ class AppriseConfig(object):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows the Apprise object to be wrapped in an Python 3.x based 'if
|
Allows the Apprise object to be wrapped in an 'if statement'.
|
||||||
statement'. True is returned if at least one service has been loaded.
|
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.
|
|
||||||
"""
|
"""
|
||||||
return True if self.configs else False
|
return True if self.configs else False
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,5 @@ class AppriseConfig:
|
||||||
def pop(self, index: int = ...) -> ConfigBase: ...
|
def pop(self, index: int = ...) -> ConfigBase: ...
|
||||||
def __getitem__(self, index: int) -> ConfigBase: ...
|
def __getitem__(self, index: int) -> ConfigBase: ...
|
||||||
def __bool__(self) -> bool: ...
|
def __bool__(self) -> bool: ...
|
||||||
def __nonzero__(self) -> bool: ...
|
|
||||||
def __iter__(self) -> Iterator[ConfigBase]: ...
|
def __iter__(self) -> Iterator[ConfigBase]: ...
|
||||||
def __len__(self) -> int: ...
|
def __len__(self) -> int: ...
|
|
@ -23,7 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import locale
|
import locale
|
||||||
import contextlib
|
import contextlib
|
||||||
|
@ -52,18 +51,11 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# gettext isn't available; no problem, just fall back to using
|
# gettext isn't available; no problem, just fall back to using
|
||||||
# the library features without multi-language support.
|
# 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
|
import builtins
|
||||||
builtins.__dict__['_'] = lambda x: x # pragma: no branch
|
builtins.__dict__['_'] = lambda x: x # pragma: no branch
|
||||||
|
|
||||||
|
|
||||||
class LazyTranslation(object):
|
class LazyTranslation:
|
||||||
"""
|
"""
|
||||||
Doesn't translate anything until str() or unicode() references
|
Doesn't translate anything until str() or unicode() references
|
||||||
are made.
|
are made.
|
||||||
|
@ -89,7 +81,7 @@ def gettext_lazy(text):
|
||||||
return LazyTranslation(text=text)
|
return LazyTranslation(text=text)
|
||||||
|
|
||||||
|
|
||||||
class AppriseLocale(object):
|
class AppriseLocale:
|
||||||
"""
|
"""
|
||||||
A wrapper class to gettext so that we can manipulate multiple lanaguages
|
A wrapper class to gettext so that we can manipulate multiple lanaguages
|
||||||
on the fly if required.
|
on the fly if required.
|
||||||
|
@ -186,7 +178,7 @@ class AppriseLocale(object):
|
||||||
"""
|
"""
|
||||||
# We want to only use the 2 character version of this language
|
# We want to only use the 2 character version of this language
|
||||||
# hence en_CA becomes en, en_US becomes en.
|
# 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:
|
if detect_fallback is False:
|
||||||
# no detection enabled; we're done
|
# no detection enabled; we're done
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -24,21 +24,13 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from xml.sax.saxutils import escape as sax_escape
|
from xml.sax.saxutils import escape as sax_escape
|
||||||
|
|
||||||
try:
|
from urllib.parse import unquote as _unquote
|
||||||
# Python 2.7
|
from urllib.parse import quote as _quote
|
||||||
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 .AppriseLocale import gettext_lazy as _
|
from .AppriseLocale import gettext_lazy as _
|
||||||
from .AppriseAsset import AppriseAsset
|
from .AppriseAsset import AppriseAsset
|
||||||
|
@ -52,7 +44,7 @@ from .utils import parse_phone_no
|
||||||
PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
PATHSPLIT_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||||
|
|
||||||
|
|
||||||
class PrivacyMode(object):
|
class PrivacyMode:
|
||||||
# Defines different privacy modes strings can be printed as
|
# Defines different privacy modes strings can be printed as
|
||||||
# Astrisk sets 4 of them: e.g. ****
|
# Astrisk sets 4 of them: e.g. ****
|
||||||
# This is used for passwords
|
# 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
|
This is the base class for all URL Manipulation
|
||||||
"""
|
"""
|
||||||
|
@ -345,7 +337,7 @@ class URLBase(object):
|
||||||
Returns:
|
Returns:
|
||||||
str: The escaped html
|
str: The escaped html
|
||||||
"""
|
"""
|
||||||
if not isinstance(html, six.string_types) or not html:
|
if not isinstance(html, str) or not html:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
# Escape HTML
|
# Escape HTML
|
||||||
|
@ -369,7 +361,7 @@ class URLBase(object):
|
||||||
encoding and errors parameters specify how to decode percent-encoded
|
encoding and errors parameters specify how to decode percent-encoded
|
||||||
sequences.
|
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
|
Python 2 & 3 since the reference to this function changed between
|
||||||
versions.
|
versions.
|
||||||
|
|
||||||
|
@ -388,20 +380,14 @@ class URLBase(object):
|
||||||
if not content:
|
if not content:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
try:
|
|
||||||
# Python v3.x
|
|
||||||
return _unquote(content, encoding=encoding, errors=errors)
|
return _unquote(content, encoding=encoding, errors=errors)
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
# Python v2.7
|
|
||||||
return _unquote(content)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def quote(content, safe='/', encoding=None, errors=None):
|
def quote(content, safe='/', encoding=None, errors=None):
|
||||||
""" Replaces single character non-ascii characters and URI specific
|
""" Replaces single character non-ascii characters and URI specific
|
||||||
ones by their %xx code.
|
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
|
Python 2 & 3 since the reference to this function changed between
|
||||||
versions.
|
versions.
|
||||||
|
|
||||||
|
@ -421,14 +407,8 @@ class URLBase(object):
|
||||||
if not content:
|
if not content:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
try:
|
|
||||||
# Python v3.x
|
|
||||||
return _quote(content, safe=safe, encoding=encoding, errors=errors)
|
return _quote(content, safe=safe, encoding=encoding, errors=errors)
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
# Python v2.7
|
|
||||||
return _quote(content, safe=safe)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pprint(content, privacy=True, mode=PrivacyMode.Outer,
|
def pprint(content, privacy=True, mode=PrivacyMode.Outer,
|
||||||
# privacy print; quoting is ignored when privacy is set to True
|
# privacy print; quoting is ignored when privacy is set to True
|
||||||
|
@ -456,7 +436,7 @@ class URLBase(object):
|
||||||
# Return 4 Asterisks
|
# Return 4 Asterisks
|
||||||
return '****'
|
return '****'
|
||||||
|
|
||||||
if not isinstance(content, six.string_types) or not content:
|
if not isinstance(content, str) or not content:
|
||||||
# Nothing more to do
|
# Nothing more to do
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -471,7 +451,7 @@ class URLBase(object):
|
||||||
def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
|
def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
|
||||||
"""Convert a mapping object or a sequence of two-element tuples
|
"""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
|
Python 2 & 3 since the reference to this function changed between
|
||||||
versions.
|
versions.
|
||||||
|
|
||||||
|
@ -575,11 +555,6 @@ class URLBase(object):
|
||||||
# Nothing further to do
|
# Nothing further to do
|
||||||
return []
|
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)
|
content = parse_phone_no(content)
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
@ -714,13 +689,13 @@ class URLBase(object):
|
||||||
|
|
||||||
for key in ('protocol', 'secure_protocol'):
|
for key in ('protocol', 'secure_protocol'):
|
||||||
schema = getattr(self, key, None)
|
schema = getattr(self, key, None)
|
||||||
if isinstance(schema, six.string_types):
|
if isinstance(schema, str):
|
||||||
schemas.add(schema)
|
schemas.add(schema)
|
||||||
|
|
||||||
elif isinstance(schema, (set, list, tuple)):
|
elif isinstance(schema, (set, list, tuple)):
|
||||||
# Support iterables list types
|
# Support iterables list types
|
||||||
for s in schema:
|
for s in schema:
|
||||||
if isinstance(s, six.string_types):
|
if isinstance(s, str):
|
||||||
schemas.add(s)
|
schemas.add(s)
|
||||||
|
|
||||||
return schemas
|
return schemas
|
||||||
|
|
|
@ -367,14 +367,7 @@ class AttachBase(URLBase):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows the Apprise object to be wrapped in an Python 3.x based 'if
|
Allows the Apprise object to be wrapped in an based 'if statement'.
|
||||||
statement'. True is returned if our content was downloaded correctly.
|
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.
|
|
||||||
"""
|
"""
|
||||||
return True if self.path else False
|
return True if self.path else False
|
||||||
|
|
|
@ -34,4 +34,3 @@ class AttachBase:
|
||||||
) -> Dict[str, Any]: ...
|
) -> Dict[str, Any]: ...
|
||||||
def __len__(self) -> int: ...
|
def __len__(self) -> int: ...
|
||||||
def __bool__(self) -> bool: ...
|
def __bool__(self) -> bool: ...
|
||||||
def __nonzero__(self) -> bool: ...
|
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from .AttachBase import AttachBase
|
from .AttachBase import AttachBase
|
||||||
|
@ -67,7 +66,7 @@ class AttachHTTP(AttachBase):
|
||||||
self.schema = 'https' if self.secure else 'http'
|
self.schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from os import listdir
|
from os import listdir
|
||||||
|
@ -88,7 +87,7 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.attachment'):
|
||||||
|
|
||||||
# Load protocol(s) if defined
|
# Load protocol(s) if defined
|
||||||
proto = getattr(plugin, 'protocol', None)
|
proto = getattr(plugin, 'protocol', None)
|
||||||
if isinstance(proto, six.string_types):
|
if isinstance(proto, str):
|
||||||
if proto not in ATTACHMENT_SCHEMA_MAP:
|
if proto not in ATTACHMENT_SCHEMA_MAP:
|
||||||
ATTACHMENT_SCHEMA_MAP[proto] = plugin
|
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
|
# Load secure protocol(s) if defined
|
||||||
protos = getattr(plugin, 'secure_protocol', None)
|
protos = getattr(plugin, 'secure_protocol', None)
|
||||||
if isinstance(protos, six.string_types):
|
if isinstance(protos, str):
|
||||||
if protos not in ATTACHMENT_SCHEMA_MAP:
|
if protos not in ATTACHMENT_SCHEMA_MAP:
|
||||||
ATTACHMENT_SCHEMA_MAP[protos] = plugin
|
ATTACHMENT_SCHEMA_MAP[protos] = plugin
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
import click
|
import click
|
||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -273,10 +272,10 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||||
# Set the theme
|
# Set the theme
|
||||||
theme=theme,
|
theme=theme,
|
||||||
|
|
||||||
# Async mode is only used for Python v3+ and allows a user to send
|
# Async mode allows a user to send all of their notifications
|
||||||
# all of their notifications asyncronously. This was made an option
|
# asynchronously. This was made an option incase there are problems
|
||||||
# incase there are problems in the future where it's better that
|
# in the future where it is better that everything runs sequentially/
|
||||||
# everything run sequentially/syncronously instead.
|
# synchronously instead.
|
||||||
async_mode=disable_async is not True,
|
async_mode=disable_async is not True,
|
||||||
|
|
||||||
# Load our plugins
|
# Load our plugins
|
||||||
|
@ -296,11 +295,11 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||||
for entry in plugins:
|
for entry in plugins:
|
||||||
protocols = [] if not entry['protocols'] else \
|
protocols = [] if not entry['protocols'] else \
|
||||||
[p for p in entry['protocols']
|
[p for p in entry['protocols']
|
||||||
if isinstance(p, six.string_types)]
|
if isinstance(p, str)]
|
||||||
protocols.extend(
|
protocols.extend(
|
||||||
[] if not entry['secure_protocols'] else
|
[] if not entry['secure_protocols'] else
|
||||||
[p for p in entry['secure_protocols']
|
[p for p in entry['secure_protocols']
|
||||||
if isinstance(p, six.string_types)])
|
if isinstance(p, str)])
|
||||||
|
|
||||||
if len(protocols) == 1:
|
if len(protocols) == 1:
|
||||||
# Simplify view by swapping {schema} with the single
|
# Simplify view by swapping {schema} with the single
|
||||||
|
|
|
@ -69,7 +69,7 @@ CONFIG_SCHEMA_MAP = {}
|
||||||
ATTACHMENT_SCHEMA_MAP = {}
|
ATTACHMENT_SCHEMA_MAP = {}
|
||||||
|
|
||||||
|
|
||||||
class NotifyType(object):
|
class NotifyType:
|
||||||
"""
|
"""
|
||||||
A simple mapping of notification types most commonly used with
|
A simple mapping of notification types most commonly used with
|
||||||
all types of logging and notification services.
|
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
|
A list of pre-defined image sizes to make it easier to work with defined
|
||||||
plugins.
|
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
|
A list of pre-defined text message formats that can be passed via the
|
||||||
apprise library.
|
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
|
A list of pre-defined modes of how to handle the text when it exceeds the
|
||||||
defined maximum message size.
|
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
|
A list of pre-defined config formats that can be passed via the
|
||||||
apprise library.
|
apprise library.
|
||||||
|
@ -175,7 +175,7 @@ CONFIG_FORMATS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ContentIncludeMode(object):
|
class ContentIncludeMode:
|
||||||
"""
|
"""
|
||||||
The different Content inclusion modes. All content based plugins will
|
The different Content inclusion modes. All content based plugins will
|
||||||
have one of these associated with it.
|
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
|
This is primarily used for handling file attachments. The idea is
|
||||||
to track the source of the attachment itself. We don't want
|
to track the source of the attachment itself. We don't want
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import yaml
|
import yaml
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -135,7 +134,7 @@ class ConfigBase(URLBase):
|
||||||
self.encoding = kwargs.get('encoding')
|
self.encoding = kwargs.get('encoding')
|
||||||
|
|
||||||
if 'format' in kwargs \
|
if 'format' in kwargs \
|
||||||
and isinstance(kwargs['format'], six.string_types):
|
and isinstance(kwargs['format'], str):
|
||||||
# Store the enforced config format
|
# Store the enforced config format
|
||||||
self.config_format = kwargs.get('format').lower()
|
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
|
# config plugin to load the data source and return unparsed content
|
||||||
# None is returned if there was an error or simply no data
|
# None is returned if there was an error or simply no data
|
||||||
content = self.read(**kwargs)
|
content = self.read(**kwargs)
|
||||||
if not isinstance(content, six.string_types):
|
if not isinstance(content, str):
|
||||||
# Set the time our content was cached at
|
# Set the time our content was cached at
|
||||||
self._cached_time = time.time()
|
self._cached_time = time.time()
|
||||||
|
|
||||||
|
@ -704,7 +703,7 @@ class ConfigBase(URLBase):
|
||||||
|
|
||||||
if not (hasattr(asset, k) and
|
if not (hasattr(asset, k) and
|
||||||
isinstance(getattr(asset, k),
|
isinstance(getattr(asset, k),
|
||||||
(bool, six.string_types))):
|
(bool, str))):
|
||||||
|
|
||||||
# We can't set a function or non-string set value
|
# We can't set a function or non-string set value
|
||||||
ConfigBase.logger.warning(
|
ConfigBase.logger.warning(
|
||||||
|
@ -715,7 +714,7 @@ class ConfigBase(URLBase):
|
||||||
# Convert to an empty string
|
# Convert to an empty string
|
||||||
v = ''
|
v = ''
|
||||||
|
|
||||||
if (isinstance(v, (bool, six.string_types))
|
if (isinstance(v, (bool, str))
|
||||||
and isinstance(getattr(asset, k), bool)):
|
and isinstance(getattr(asset, k), bool)):
|
||||||
|
|
||||||
# If the object in the Asset is a boolean, then
|
# If the object in the Asset is a boolean, then
|
||||||
|
@ -723,7 +722,7 @@ class ConfigBase(URLBase):
|
||||||
# match that.
|
# match that.
|
||||||
setattr(asset, k, parse_bool(v))
|
setattr(asset, k, parse_bool(v))
|
||||||
|
|
||||||
elif isinstance(v, six.string_types):
|
elif isinstance(v, str):
|
||||||
# Set our asset object with the new value
|
# Set our asset object with the new value
|
||||||
setattr(asset, k, v.strip())
|
setattr(asset, k, v.strip())
|
||||||
|
|
||||||
|
@ -738,7 +737,7 @@ class ConfigBase(URLBase):
|
||||||
global_tags = set()
|
global_tags = set()
|
||||||
|
|
||||||
tags = result.get('tag', None)
|
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
|
# Store any preset tags
|
||||||
global_tags = set(parse_list(tags))
|
global_tags = set(parse_list(tags))
|
||||||
|
|
||||||
|
@ -746,7 +745,7 @@ class ConfigBase(URLBase):
|
||||||
# include root directive
|
# include root directive
|
||||||
#
|
#
|
||||||
includes = result.get('include', None)
|
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
|
# Support a single inline string or multiple ones separated by a
|
||||||
# comma and/or space
|
# comma and/or space
|
||||||
includes = parse_urls(includes)
|
includes = parse_urls(includes)
|
||||||
|
@ -758,7 +757,7 @@ class ConfigBase(URLBase):
|
||||||
# Iterate over each config URL
|
# Iterate over each config URL
|
||||||
for no, url in enumerate(includes):
|
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
|
# Support a single inline string or multiple ones separated by
|
||||||
# a comma and/or space
|
# a comma and/or space
|
||||||
configs.extend(parse_urls(url))
|
configs.extend(parse_urls(url))
|
||||||
|
@ -786,7 +785,7 @@ class ConfigBase(URLBase):
|
||||||
loggable_url = url if not asset.secure_logging \
|
loggable_url = url if not asset.secure_logging \
|
||||||
else cwe312_url(url)
|
else cwe312_url(url)
|
||||||
|
|
||||||
if isinstance(url, six.string_types):
|
if isinstance(url, str):
|
||||||
# We're just a simple URL string...
|
# We're just a simple URL string...
|
||||||
schema = GET_SCHEMA_RE.match(url)
|
schema = GET_SCHEMA_RE.match(url)
|
||||||
if schema is None:
|
if schema is None:
|
||||||
|
@ -817,9 +816,6 @@ class ConfigBase(URLBase):
|
||||||
# can at least tell the end user what entries were ignored
|
# can at least tell the end user what entries were ignored
|
||||||
# due to errors
|
# 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
|
# Track the URL to-load
|
||||||
|
@ -870,9 +866,6 @@ class ConfigBase(URLBase):
|
||||||
|
|
||||||
# We are a url string with additional unescaped options
|
# We are a url string with additional unescaped options
|
||||||
if isinstance(entries, dict):
|
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
|
# Tags you just can't over-ride
|
||||||
|
@ -1114,7 +1107,7 @@ class ConfigBase(URLBase):
|
||||||
r'^(choice:)?string',
|
r'^(choice:)?string',
|
||||||
meta.get('type'),
|
meta.get('type'),
|
||||||
re.IGNORECASE) \
|
re.IGNORECASE) \
|
||||||
and not isinstance(value, six.string_types):
|
and not isinstance(value, str):
|
||||||
|
|
||||||
# Ensure our format is as expected
|
# Ensure our format is as expected
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
@ -1167,19 +1160,8 @@ class ConfigBase(URLBase):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows the Apprise object to be wrapped in an Python 3.x based 'if
|
Allows the Apprise object to be wrapped in an 'if statement'.
|
||||||
statement'. True is returned if our content was downloaded correctly.
|
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.
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(self._cached_servers, list):
|
if not isinstance(self._cached_servers, list):
|
||||||
# Generate ourselves a list of content we can pull from
|
# Generate ourselves a list of content we can pull from
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import io
|
|
||||||
import os
|
import os
|
||||||
from .ConfigBase import ConfigBase
|
from .ConfigBase import ConfigBase
|
||||||
from ..common import ConfigFormat
|
from ..common import ConfigFormat
|
||||||
|
@ -119,9 +118,7 @@ class ConfigFile(ConfigBase):
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Python 3 just supports open(), however to remain compatible with
|
with open(self.path, "rt", encoding=self.encoding) as f:
|
||||||
# Python 2, we use the io module
|
|
||||||
with io.open(self.path, "rt", encoding=self.encoding) as f:
|
|
||||||
# Store our content for parsing
|
# Store our content for parsing
|
||||||
response = f.read()
|
response = f.read()
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from .ConfigBase import ConfigBase
|
from .ConfigBase import ConfigBase
|
||||||
from ..common import ConfigFormat
|
from ..common import ConfigFormat
|
||||||
|
@ -81,7 +80,7 @@ class ConfigHTTP(ConfigBase):
|
||||||
self.schema = 'https' if self.secure else 'http'
|
self.schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
|
@ -87,28 +86,8 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.config'):
|
||||||
globals()[plugin_name] = plugin
|
globals()[plugin_name] = plugin
|
||||||
|
|
||||||
fn = getattr(plugin, 'schemas', None)
|
fn = getattr(plugin, 'schemas', None)
|
||||||
try:
|
|
||||||
schemas = set([]) if not callable(fn) else fn(plugin)
|
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)
|
|
||||||
|
|
||||||
# map our schema to our plugin
|
# map our schema to our plugin
|
||||||
for schema in schemas:
|
for schema in schemas:
|
||||||
if schema in CONFIG_SCHEMA_MAP:
|
if schema in CONFIG_SCHEMA_MAP:
|
||||||
|
|
|
@ -23,18 +23,12 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from .common import NotifyFormat
|
from .common import NotifyFormat
|
||||||
from .URLBase import URLBase
|
from .URLBase import URLBase
|
||||||
|
|
||||||
if six.PY2:
|
from html.parser import HTMLParser
|
||||||
from HTMLParser import HTMLParser
|
|
||||||
|
|
||||||
else:
|
|
||||||
from html.parser import HTMLParser
|
|
||||||
|
|
||||||
|
|
||||||
def convert_between(from_format, to_format, content):
|
def convert_between(from_format, to_format, content):
|
||||||
|
@ -80,10 +74,6 @@ def html_to_text(content):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parser = HTMLConverter()
|
parser = HTMLConverter()
|
||||||
if six.PY2:
|
|
||||||
# Python 2.7 requires an additional parsing to un-escape characters
|
|
||||||
content = parser.unescape(content)
|
|
||||||
|
|
||||||
parser.feed(content)
|
parser.feed(content)
|
||||||
parser.close()
|
parser.close()
|
||||||
return parser.converted
|
return parser.converted
|
||||||
|
@ -125,20 +115,6 @@ class HTMLConverter(HTMLParser, object):
|
||||||
string = ''.join(self._finalize(self._result))
|
string = ''.join(self._finalize(self._result))
|
||||||
self.converted = string.strip()
|
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):
|
def _finalize(self, result):
|
||||||
"""
|
"""
|
||||||
Combines and strips consecutive strings, then converts consecutive
|
Combines and strips consecutive strings, then converts consecutive
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# 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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import six
|
|
||||||
from ..plugins.NotifyBase import NotifyBase
|
from ..plugins.NotifyBase import NotifyBase
|
||||||
from ..utils import URL_DETAILS_RE
|
from ..utils import URL_DETAILS_RE
|
||||||
from ..utils import parse_url
|
from ..utils import parse_url
|
||||||
|
@ -73,7 +72,7 @@ class CustomNotifyPlugin(NotifyBase):
|
||||||
parsed from the provided URL into our supported matrix structure.
|
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 ' \
|
msg = 'An invalid custom notify url/schema ({}) provided in ' \
|
||||||
'function {}.'.format(url, send_func.__name__)
|
'function {}.'.format(url, send_func.__name__)
|
||||||
logger.warning(msg)
|
logger.warning(msg)
|
||||||
|
@ -112,7 +111,7 @@ class CustomNotifyPlugin(NotifyBase):
|
||||||
class CustomNotifyPluginWrapper(CustomNotifyPlugin):
|
class CustomNotifyPluginWrapper(CustomNotifyPlugin):
|
||||||
|
|
||||||
# Our Service Name
|
# 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)
|
and name else 'Custom - {}'.format(plugin_name)
|
||||||
|
|
||||||
# Store our matched schema
|
# Store our matched schema
|
||||||
|
|
|
@ -66,7 +66,7 @@ logging.Logger.deprecate = deprecate
|
||||||
logger = logging.getLogger(LOGGER_NAME)
|
logger = logging.getLogger(LOGGER_NAME)
|
||||||
|
|
||||||
|
|
||||||
class LogCapture(object):
|
class LogCapture:
|
||||||
"""
|
"""
|
||||||
A class used to allow one to instantiate loggers that write to
|
A class used to allow one to instantiate loggers that write to
|
||||||
memory for temporary purposes. e.g.:
|
memory for temporary purposes. e.g.:
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
|
@ -137,7 +136,7 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
super(NotifyAppriseAPI, self).__init__(**kwargs)
|
super(NotifyAppriseAPI, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
|
|
||||||
self.token = validate_regex(
|
self.token = validate_regex(
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
#
|
#
|
||||||
# API: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#python
|
# API: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#python
|
||||||
#
|
#
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
@ -76,7 +75,7 @@ BARK_SOUNDS = (
|
||||||
|
|
||||||
|
|
||||||
# Supported Level Entries
|
# Supported Level Entries
|
||||||
class NotifyBarkLevel(object):
|
class NotifyBarkLevel:
|
||||||
"""
|
"""
|
||||||
Defines the Bark Level options
|
Defines the Bark Level options
|
||||||
"""
|
"""
|
||||||
|
@ -217,10 +216,10 @@ class NotifyBark(NotifyBase):
|
||||||
|
|
||||||
# Assign our category
|
# Assign our category
|
||||||
self.category = \
|
self.category = \
|
||||||
category if isinstance(category, six.string_types) else None
|
category if isinstance(category, str) else None
|
||||||
|
|
||||||
# Assign our group
|
# 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
|
# Initialize device list
|
||||||
self.targets = parse_list(targets)
|
self.targets = parse_list(targets)
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
|
|
||||||
from ..URLBase import URLBase
|
from ..URLBase import URLBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
@ -37,14 +36,9 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
from ..AppriseAttachment import AppriseAttachment
|
from ..AppriseAttachment import AppriseAttachment
|
||||||
|
|
||||||
|
|
||||||
if six.PY3:
|
# Wrap our base with the asyncio wrapper
|
||||||
# Wrap our base with the asyncio wrapper
|
from ..py3compat.asyncio import AsyncNotifyBase
|
||||||
from ..py3compat.asyncio import AsyncNotifyBase
|
BASE_OBJECT = AsyncNotifyBase
|
||||||
BASE_OBJECT = AsyncNotifyBase
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Python v2.7 (backwards compatibility)
|
|
||||||
BASE_OBJECT = URLBase
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyBase(BASE_OBJECT):
|
class NotifyBase(BASE_OBJECT):
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
import hmac
|
import hmac
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
@ -181,7 +180,7 @@ class NotifyBoxcar(NotifyBase):
|
||||||
self.tags.append(DEFAULT_TAG)
|
self.tags.append(DEFAULT_TAG)
|
||||||
targets = []
|
targets = []
|
||||||
|
|
||||||
elif isinstance(targets, six.string_types):
|
elif isinstance(targets, str):
|
||||||
targets = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
targets = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
||||||
targets,
|
targets,
|
||||||
))]
|
))]
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
# (both user and password) from the API Details section from within your
|
# (both user and password) from the API Details section from within your
|
||||||
# account profile area: https://d7networks.com/accounts/profile/
|
# account profile area: https://d7networks.com/accounts/profile/
|
||||||
|
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
import base64
|
import base64
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
@ -54,7 +53,7 @@ D7NETWORKS_HTTP_ERROR_MAP = {
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class D7SMSPriority(object):
|
class D7SMSPriority:
|
||||||
"""
|
"""
|
||||||
D7 Networks SMS Message Priority
|
D7 Networks SMS Message Priority
|
||||||
"""
|
"""
|
||||||
|
@ -192,7 +191,7 @@ class NotifyD7Networks(NotifyBase):
|
||||||
|
|
||||||
# Setup our source address (if defined)
|
# Setup our source address (if defined)
|
||||||
self.source = None \
|
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):
|
if not (self.user and self.password):
|
||||||
msg = 'A D7 Networks user/pass was not provided.'
|
msg = 'A D7 Networks user/pass was not provided.'
|
||||||
|
@ -232,9 +231,9 @@ class NotifyD7Networks(NotifyBase):
|
||||||
|
|
||||||
auth = '{user}:{password}'.format(
|
auth = '{user}:{password}'.format(
|
||||||
user=self.user, password=self.password)
|
user=self.user, password=self.password)
|
||||||
if six.PY3:
|
|
||||||
# Python 3's versio of b64encode() expects a byte array and not
|
# Python 3's versio of b64encode() expects a byte array and not
|
||||||
# a string. To accomodate this, we encode the content here
|
# a string. To accommodate this, we encode the content here
|
||||||
auth = auth.encode('utf-8')
|
auth = auth.encode('utf-8')
|
||||||
|
|
||||||
# Prepare our headers
|
# Prepare our headers
|
||||||
|
|
|
@ -60,7 +60,7 @@ try:
|
||||||
from dbus.mainloop.glib import DBusGMainLoop
|
from dbus.mainloop.glib import DBusGMainLoop
|
||||||
LOOP_GLIB = DBusGMainLoop()
|
LOOP_GLIB = DBusGMainLoop()
|
||||||
|
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
# No problem
|
# No problem
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ MAINLOOP_MAP = {
|
||||||
|
|
||||||
|
|
||||||
# Urgencies
|
# Urgencies
|
||||||
class DBusUrgency(object):
|
class DBusUrgency:
|
||||||
LOW = 0
|
LOW = 0
|
||||||
NORMAL = 1
|
NORMAL = 1
|
||||||
HIGH = 2
|
HIGH = 2
|
||||||
|
@ -161,10 +161,11 @@ class NotifyDBus(NotifyBase):
|
||||||
service_url = 'http://www.freedesktop.org/Software/dbus/'
|
service_url = 'http://www.freedesktop.org/Software/dbus/'
|
||||||
|
|
||||||
# The default protocols
|
# 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
|
# 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
|
# Python v2. So converting the result set back into a list makes us
|
||||||
# compatible
|
# compatible
|
||||||
|
# TODO: Review after dropping support for Python 2.
|
||||||
protocol = list(MAINLOOP_MAP.keys())
|
protocol = list(MAINLOOP_MAP.keys())
|
||||||
|
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# 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
|
from ..utils import parse_bool
|
||||||
|
|
||||||
|
|
||||||
class DapnetPriority(object):
|
class DapnetPriority:
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
EMERGENCY = 1
|
EMERGENCY = 1
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import smtplib
|
import smtplib
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.application import MIMEApplication
|
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')
|
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
|
This class is just used in conjunction of the default emailers
|
||||||
to best formulate a login to it using the data detected
|
to best formulate a login to it using the data detected
|
||||||
|
@ -60,7 +59,7 @@ class WebBaseLogin(object):
|
||||||
|
|
||||||
|
|
||||||
# Secure Email Modes
|
# Secure Email Modes
|
||||||
class SecureMailMode(object):
|
class SecureMailMode:
|
||||||
SSL = "ssl"
|
SSL = "ssl"
|
||||||
STARTTLS = "starttls"
|
STARTTLS = "starttls"
|
||||||
|
|
||||||
|
@ -480,11 +479,11 @@ class NotifyEmail(NotifyBase):
|
||||||
|
|
||||||
# Now detect the SMTP Server
|
# Now detect the SMTP Server
|
||||||
self.smtp_host = \
|
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
|
# Now detect secure mode
|
||||||
self.secure_mode = self.default_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()
|
else secure_mode.lower()
|
||||||
if self.secure_mode not in SECURE_MODES:
|
if self.secure_mode not in SECURE_MODES:
|
||||||
msg = 'The secure mode specified ({}) is invalid.'\
|
msg = 'The secure mode specified ({}) is invalid.'\
|
||||||
|
@ -684,7 +683,6 @@ class NotifyEmail(NotifyBase):
|
||||||
# Strip target out of reply_to list if in To
|
# Strip target out of reply_to list if in To
|
||||||
reply_to = (self.reply_to - set([to_addr]))
|
reply_to = (self.reply_to - set([to_addr]))
|
||||||
|
|
||||||
try:
|
|
||||||
# Format our cc addresses to support the Name field
|
# Format our cc addresses to support the Name field
|
||||||
cc = [formataddr(
|
cc = [formataddr(
|
||||||
(self.names.get(addr, False), addr), charset='utf-8')
|
(self.names.get(addr, False), addr), charset='utf-8')
|
||||||
|
@ -701,22 +699,6 @@ class NotifyEmail(NotifyBase):
|
||||||
(self.names.get(addr, False), addr), charset='utf-8')
|
(self.names.get(addr, False), addr), charset='utf-8')
|
||||||
for addr in reply_to]
|
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]
|
|
||||||
|
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
'Email From: {} <{}>'.format(from_name, self.from_addr))
|
'Email From: {} <{}>'.format(from_name, self.from_addr))
|
||||||
self.logger.debug('Email To: {}'.format(to_addr))
|
self.logger.debug('Email To: {}'.format(to_addr))
|
||||||
|
@ -781,25 +763,11 @@ class NotifyEmail(NotifyBase):
|
||||||
base[k] = Header(v, self._get_charset(v))
|
base[k] = Header(v, self._get_charset(v))
|
||||||
|
|
||||||
base['Subject'] = Header(title, self._get_charset(title))
|
base['Subject'] = Header(title, self._get_charset(title))
|
||||||
try:
|
|
||||||
base['From'] = formataddr(
|
base['From'] = formataddr(
|
||||||
(from_name if from_name else False, self.from_addr),
|
(from_name if from_name else False, self.from_addr),
|
||||||
charset='utf-8')
|
charset='utf-8')
|
||||||
base['To'] = formataddr((to_name, to_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)
|
base['Message-ID'] = make_msgid(domain=self.smtp_host)
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
# Python v2.x Support (no domain keyword)
|
|
||||||
base['Message-ID'] = make_msgid()
|
|
||||||
|
|
||||||
base['Date'] = \
|
base['Date'] = \
|
||||||
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||||
base['X-Application'] = self.app_id
|
base['X-Application'] = self.app_id
|
||||||
|
|
|
@ -677,7 +677,7 @@ class NotifyEmby(NotifyBase):
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""
|
"""
|
||||||
Deconstructor
|
Destructor
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.logout()
|
self.logout()
|
||||||
|
@ -694,20 +694,20 @@ class NotifyEmby(NotifyBase):
|
||||||
# - https://bugs.python.org/issue29288
|
# - https://bugs.python.org/issue29288
|
||||||
#
|
#
|
||||||
# A ~similar~ issue can be identified here in the requests
|
# 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
|
# - https://github.com/kennethreitz/requests/issues/3578
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
# The actual exception is `ModuleNotFoundError` however ImportError
|
# 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
|
# than v3.6
|
||||||
|
|
||||||
# Python code that makes early calls to sys.exit() can cause
|
# 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
|
# Python, this causes the `sys` library to no longer be
|
||||||
# available. The stack overflow also goes on to suggest that
|
# 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.
|
# which is the case here.
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/67218341/\
|
# https://stackoverflow.com/questions/67218341/\
|
||||||
|
@ -719,6 +719,6 @@ class NotifyEmby(NotifyBase):
|
||||||
# /1481488/what-is-the-del-method-and-how-do-i-call-it
|
# /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)
|
# 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.
|
# the end user if we don't have to.
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
# - https://github.com/E2OpenPlugins/e2openplugin-OpenWebif/wiki/\
|
# - https://github.com/E2OpenPlugins/e2openplugin-OpenWebif/wiki/\
|
||||||
# OpenWebif-API-documentation#message
|
# OpenWebif-API-documentation#message
|
||||||
|
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ from ..common import NotifyType
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Enigma2MessageType(object):
|
class Enigma2MessageType:
|
||||||
# Defines the Enigma2 notification types Apprise can map to
|
# Defines the Enigma2 notification types Apprise can map to
|
||||||
INFO = 1
|
INFO = 1
|
||||||
WARNING = 2
|
WARNING = 2
|
||||||
|
@ -169,7 +168,7 @@ class NotifyEnigma2(NotifyBase):
|
||||||
self.timeout = self.template_args['timeout']['default']
|
self.timeout = self.template_args['timeout']['default']
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
|
|
@ -45,7 +45,6 @@
|
||||||
#
|
#
|
||||||
# If you Generate a new private key, it will provide a .json file
|
# If you Generate a new private key, it will provide a .json file
|
||||||
# You will need this in order to send an apprise messag
|
# You will need this in order to send an apprise messag
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from ..NotifyBase import NotifyBase
|
from ..NotifyBase import NotifyBase
|
||||||
|
@ -74,7 +73,7 @@ except ImportError:
|
||||||
# cryptography is the dependency of the .oauth library
|
# cryptography is the dependency of the .oauth library
|
||||||
|
|
||||||
# Create a dummy object for init() call to work
|
# Create a dummy object for init() call to work
|
||||||
class GoogleOAuth(object):
|
class GoogleOAuth:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -228,7 +227,7 @@ class NotifyFCM(NotifyBase):
|
||||||
else:
|
else:
|
||||||
# Setup our mode
|
# Setup our mode
|
||||||
self.mode = NotifyFCM.template_tokens['mode']['default'] \
|
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:
|
if self.mode and self.mode not in FCM_MODES:
|
||||||
msg = 'The FCM mode specified ({}) is invalid.'.format(mode)
|
msg = 'The FCM mode specified ({}) is invalid.'.format(mode)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
|
@ -31,13 +31,12 @@
|
||||||
# https://firebase.google.com/docs/reference/fcm/rest/v1/\
|
# https://firebase.google.com/docs/reference/fcm/rest/v1/\
|
||||||
# projects.messages#androidnotification
|
# projects.messages#androidnotification
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
from ...utils import parse_bool
|
from ...utils import parse_bool
|
||||||
from ...common import NotifyType
|
from ...common import NotifyType
|
||||||
from ...AppriseAsset import AppriseAsset
|
from ...AppriseAsset import AppriseAsset
|
||||||
|
|
||||||
|
|
||||||
class FCMColorManager(object):
|
class FCMColorManager:
|
||||||
"""
|
"""
|
||||||
A Simple object to accept either a boolean value
|
A Simple object to accept either a boolean value
|
||||||
- True: Use colors provided by Apprise
|
- True: Use colors provided by Apprise
|
||||||
|
@ -63,7 +62,7 @@ class FCMColorManager(object):
|
||||||
|
|
||||||
# Prepare our color
|
# Prepare our color
|
||||||
self.color = color
|
self.color = color
|
||||||
if isinstance(color, six.string_types):
|
if isinstance(color, str):
|
||||||
self.color = self.__color_rgb.match(color)
|
self.color = self.__color_rgb.match(color)
|
||||||
if self.color:
|
if self.color:
|
||||||
# Store our RGB value as #rrggbb
|
# Store our RGB value as #rrggbb
|
||||||
|
@ -112,16 +111,8 @@ class FCMColorManager(object):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows this object to be wrapped in an Python 3.x based 'if
|
Allows this object to be wrapped in an 'if statement'.
|
||||||
statement'. True is returned if a color was loaded
|
True is returned if a color was loaded
|
||||||
"""
|
"""
|
||||||
return True if self.color is True or \
|
return True if self.color is True or \
|
||||||
isinstance(self.color, six.string_types) else False
|
isinstance(self.color, str) 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
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# 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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
class FCMMode(object):
|
class FCMMode:
|
||||||
"""
|
"""
|
||||||
Define the Firebase Cloud Messaging Modes
|
Define the Firebase Cloud Messaging Modes
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
# 2. Click Generate New Private Key, then confirm by clicking Generate Key.
|
# 2. Click Generate New Private Key, then confirm by clicking Generate Key.
|
||||||
# 3. Securely store the JSON file containing the key.
|
# 3. Securely store the JSON file containing the key.
|
||||||
|
|
||||||
import io
|
|
||||||
import requests
|
import requests
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
@ -41,26 +40,13 @@ from cryptography.hazmat.primitives import asymmetric
|
||||||
from cryptography.exceptions import UnsupportedAlgorithm
|
from cryptography.exceptions import UnsupportedAlgorithm
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
from urllib.parse import urlencode as _urlencode
|
||||||
|
|
||||||
from ...logger import logger
|
from ...logger import logger
|
||||||
|
|
||||||
try:
|
|
||||||
# Python 2.7
|
|
||||||
from urllib import urlencode as _urlencode
|
|
||||||
|
|
||||||
except ImportError:
|
class GoogleOAuth:
|
||||||
# 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):
|
|
||||||
"""
|
"""
|
||||||
A OAuth simplified implimentation to Google's Firebase Cloud Messaging
|
A OAuth simplified implimentation to Google's Firebase Cloud Messaging
|
||||||
|
|
||||||
|
@ -127,7 +113,7 @@ class GoogleOAuth(object):
|
||||||
self.__access_token_expiry = datetime.utcnow()
|
self.__access_token_expiry = datetime.utcnow()
|
||||||
|
|
||||||
try:
|
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())
|
self.content = json.loads(fp.read())
|
||||||
|
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
|
|
|
@ -33,7 +33,7 @@ from .common import (FCMMode, FCM_MODES)
|
||||||
from ...logger import logger
|
from ...logger import logger
|
||||||
|
|
||||||
|
|
||||||
class NotificationPriority(object):
|
class NotificationPriority:
|
||||||
"""
|
"""
|
||||||
Defines the Notification Priorities as described on:
|
Defines the Notification Priorities as described on:
|
||||||
https://firebase.google.com/docs/reference/fcm/rest/v1/\
|
https://firebase.google.com/docs/reference/fcm/rest/v1/\
|
||||||
|
@ -63,7 +63,7 @@ class NotificationPriority(object):
|
||||||
HIGH = 'HIGH'
|
HIGH = 'HIGH'
|
||||||
|
|
||||||
|
|
||||||
class FCMPriority(object):
|
class FCMPriority:
|
||||||
"""
|
"""
|
||||||
Defines our accepted priorites
|
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
|
A Simple object to make it easier to work with FCM set priorities
|
||||||
"""
|
"""
|
||||||
|
@ -242,14 +242,7 @@ class FCMPriorityManager(object):
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows this object to be wrapped in an Python 3.x based 'if
|
Allows this object to be wrapped in an 'if statement'.
|
||||||
statement'. True is returned if a priority was loaded
|
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
|
|
||||||
"""
|
"""
|
||||||
return True if self.priority else False
|
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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
|
@ -137,11 +136,11 @@ class NotifyForm(NotifyBase):
|
||||||
super(NotifyForm, self).__init__(**kwargs)
|
super(NotifyForm, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = ''
|
self.fullpath = ''
|
||||||
|
|
||||||
self.method = self.template_args['method']['default'] \
|
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:
|
if self.method not in METHODS:
|
||||||
msg = 'The method specified ({}) is invalid.'.format(method)
|
msg = 'The method specified ({}) is invalid.'.format(method)
|
||||||
|
|
|
@ -60,7 +60,7 @@ except (ImportError, ValueError, AttributeError):
|
||||||
|
|
||||||
|
|
||||||
# Urgencies
|
# Urgencies
|
||||||
class GnomeUrgency(object):
|
class GnomeUrgency:
|
||||||
LOW = 0
|
LOW = 0
|
||||||
NORMAL = 1
|
NORMAL = 1
|
||||||
HIGH = 2
|
HIGH = 2
|
||||||
|
|
|
@ -41,7 +41,7 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class GotifyPriority(object):
|
class GotifyPriority:
|
||||||
LOW = 0
|
LOW = 0
|
||||||
MODERATE = 3
|
MODERATE = 3
|
||||||
NORMAL = 5
|
NORMAL = 5
|
||||||
|
|
|
@ -46,7 +46,7 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class GrowlPriority(object):
|
class GrowlPriority:
|
||||||
LOW = -2
|
LOW = -2
|
||||||
MODERATE = -1
|
MODERATE = -1
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
import base64
|
import base64
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
@ -139,11 +138,11 @@ class NotifyJSON(NotifyBase):
|
||||||
super(NotifyJSON, self).__init__(**kwargs)
|
super(NotifyJSON, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = ''
|
self.fullpath = ''
|
||||||
|
|
||||||
self.method = self.template_args['method']['default'] \
|
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:
|
if self.method not in METHODS:
|
||||||
msg = 'The method specified ({}) is invalid.'.format(method)
|
msg = 'The method specified ({}) is invalid.'.format(method)
|
||||||
|
|
|
@ -63,7 +63,7 @@ JOIN_IMAGE_XY = NotifyImageSize.XY_72
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class JoinPriority(object):
|
class JoinPriority:
|
||||||
LOW = -2
|
LOW = -2
|
||||||
MODERATE = -1
|
MODERATE = -1
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
|
|
|
@ -85,7 +85,6 @@
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from .NotifyBase import NotifyBase
|
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)
|
LAMETRIC_IS_APP_TOKEN = re.compile(r'^[a-z0-9]{80,}==$', re.I)
|
||||||
|
|
||||||
|
|
||||||
class LametricMode(object):
|
class LametricMode:
|
||||||
"""
|
"""
|
||||||
Define Lametric Notification Modes
|
Define Lametric Notification Modes
|
||||||
"""
|
"""
|
||||||
|
@ -121,7 +120,7 @@ LAMETRIC_MODES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LametricPriority(object):
|
class LametricPriority:
|
||||||
"""
|
"""
|
||||||
Priority of the message
|
Priority of the message
|
||||||
"""
|
"""
|
||||||
|
@ -158,7 +157,7 @@ LAMETRIC_PRIORITIES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LametricIconType(object):
|
class LametricIconType:
|
||||||
"""
|
"""
|
||||||
Represents the nature of notification.
|
Represents the nature of notification.
|
||||||
"""
|
"""
|
||||||
|
@ -184,7 +183,7 @@ LAMETRIC_ICON_TYPES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LametricSoundCategory(object):
|
class LametricSoundCategory:
|
||||||
"""
|
"""
|
||||||
Define Sound Categories
|
Define Sound Categories
|
||||||
"""
|
"""
|
||||||
|
@ -192,7 +191,7 @@ class LametricSoundCategory(object):
|
||||||
ALARMS = "alarms"
|
ALARMS = "alarms"
|
||||||
|
|
||||||
|
|
||||||
class LametricSound(object):
|
class LametricSound:
|
||||||
"""
|
"""
|
||||||
There are 2 categories of sounds, to make things simple we just lump them
|
There are 2 categories of sounds, to make things simple we just lump them
|
||||||
all togther in one class object.
|
all togther in one class object.
|
||||||
|
@ -471,7 +470,7 @@ class NotifyLametric(NotifyBase):
|
||||||
super(NotifyLametric, self).__init__(**kwargs)
|
super(NotifyLametric, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.mode = mode.strip().lower() \
|
self.mode = mode.strip().lower() \
|
||||||
if isinstance(mode, six.string_types) \
|
if isinstance(mode, str) \
|
||||||
else self.template_args['mode']['default']
|
else self.template_args['mode']['default']
|
||||||
|
|
||||||
# Default Cloud Argument
|
# Default Cloud Argument
|
||||||
|
@ -543,7 +542,7 @@ class NotifyLametric(NotifyBase):
|
||||||
# assign our icon (if it was defined); we also eliminate
|
# assign our icon (if it was defined); we also eliminate
|
||||||
# any hashtag (#) entries that might be present
|
# any hashtag (#) entries that might be present
|
||||||
self.icon = re.search(r'[#\s]*(?P<value>.+?)\s*$', icon) \
|
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:
|
if icon_type not in LAMETRIC_ICON_TYPES:
|
||||||
self.icon_type = self.template_args['icon_type']['default']
|
self.icon_type = self.template_args['icon_type']['default']
|
||||||
|
@ -557,7 +556,7 @@ class NotifyLametric(NotifyBase):
|
||||||
cycles > self.template_args['cycles']['min']) else cycles
|
cycles > self.template_args['cycles']['min']) else cycles
|
||||||
|
|
||||||
self.sound = None
|
self.sound = None
|
||||||
if isinstance(sound, six.string_types):
|
if isinstance(sound, str):
|
||||||
# If sound is set, get it's match
|
# If sound is set, get it's match
|
||||||
self.sound = self.sound_lookup(sound.strip().lower())
|
self.sound = self.sound_lookup(sound.strip().lower())
|
||||||
if self.sound is None:
|
if self.sound is None:
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
# /blob/master/src/paho/mqtt/client.py
|
# /blob/master/src/paho/mqtt/client.py
|
||||||
import ssl
|
import ssl
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
|
@ -46,11 +45,6 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
# Default our global support flag
|
# Default our global support flag
|
||||||
NOTIFY_MQTT_SUPPORT_ENABLED = False
|
NOTIFY_MQTT_SUPPORT_ENABLED = False
|
||||||
|
|
||||||
if six.PY2:
|
|
||||||
# handle Python v2.7 suport
|
|
||||||
class ConnectionError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 3rd party modules
|
# 3rd party modules
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
|
|
@ -41,7 +41,7 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class MSG91Route(object):
|
class MSG91Route:
|
||||||
"""
|
"""
|
||||||
Transactional SMS Routes
|
Transactional SMS Routes
|
||||||
route=1 for promotional, route=4 for transactional SMS.
|
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
|
Optional value that can be specified on the MSG91 api
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
|
@ -88,13 +89,6 @@ from ..utils import TemplateType
|
||||||
from ..AppriseAttachment import AppriseAttachment
|
from ..AppriseAttachment import AppriseAttachment
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
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):
|
class NotifyMSTeams(NotifyBase):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -74,7 +74,7 @@ MAILGUN_HTTP_ERROR_MAP = {
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class MailgunRegion(object):
|
class MailgunRegion:
|
||||||
US = 'us'
|
US = 'us'
|
||||||
EU = 'eu'
|
EU = 'eu'
|
||||||
|
|
||||||
|
@ -383,18 +383,10 @@ class NotifyMailgun(NotifyBase):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
|
||||||
reply_to = formataddr(
|
reply_to = formataddr(
|
||||||
(self.from_name if self.from_name else False,
|
(self.from_name if self.from_name else False,
|
||||||
self.from_addr), charset='utf-8')
|
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))
|
|
||||||
|
|
||||||
# Prepare our payload
|
# Prepare our payload
|
||||||
payload = {
|
payload = {
|
||||||
# pass skip-verification switch upstream too
|
# pass skip-verification switch upstream too
|
||||||
|
@ -461,34 +453,18 @@ class NotifyMailgun(NotifyBase):
|
||||||
# Strip target out of bcc list if in To
|
# Strip target out of bcc list if in To
|
||||||
bcc = (bcc - set([to_addr[1]]))
|
bcc = (bcc - set([to_addr[1]]))
|
||||||
|
|
||||||
try:
|
# Prepare our `to`
|
||||||
# Prepare our to
|
|
||||||
to.append(formataddr(to_addr, charset='utf-8'))
|
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
|
# Prepare our To
|
||||||
payload['to'] = ','.join(to)
|
payload['to'] = ','.join(to)
|
||||||
|
|
||||||
if cc:
|
if cc:
|
||||||
try:
|
|
||||||
# Format our cc addresses to support the Name field
|
# Format our cc addresses to support the Name field
|
||||||
payload['cc'] = ','.join([formataddr(
|
payload['cc'] = ','.join([formataddr(
|
||||||
(self.names.get(addr, False), addr), charset='utf-8')
|
(self.names.get(addr, False), addr), charset='utf-8')
|
||||||
for addr in cc])
|
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 bcc addresses to support the Name field
|
# Format our bcc addresses to support the Name field
|
||||||
if bcc:
|
if bcc:
|
||||||
payload['bcc'] = ','.join(bcc)
|
payload['bcc'] = ','.join(bcc)
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
# - https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst
|
# - https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
@ -67,7 +66,7 @@ IS_ROOM_ID = re.compile(
|
||||||
r'(?P<home_server>[a-z0-9.-]+))?\s*$', re.I)
|
r'(?P<home_server>[a-z0-9.-]+))?\s*$', re.I)
|
||||||
|
|
||||||
|
|
||||||
class MatrixMessageType(object):
|
class MatrixMessageType:
|
||||||
"""
|
"""
|
||||||
The Matrix Message types
|
The Matrix Message types
|
||||||
"""
|
"""
|
||||||
|
@ -82,7 +81,7 @@ MATRIX_MESSAGE_TYPES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MatrixWebhookMode(object):
|
class MatrixWebhookMode:
|
||||||
# Webhook Mode is disabled
|
# Webhook Mode is disabled
|
||||||
DISABLED = "off"
|
DISABLED = "off"
|
||||||
|
|
||||||
|
@ -263,7 +262,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
|
|
||||||
# Setup our mode
|
# Setup our mode
|
||||||
self.mode = self.template_args['mode']['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 and self.mode not in MATRIX_WEBHOOK_MODES:
|
if self.mode and self.mode not in MATRIX_WEBHOOK_MODES:
|
||||||
msg = 'The mode specified ({}) is invalid.'.format(mode)
|
msg = 'The mode specified ({}) is invalid.'.format(mode)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
@ -271,7 +270,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
|
|
||||||
# Setup our message type
|
# Setup our message type
|
||||||
self.msgtype = self.template_args['msgtype']['default'] \
|
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:
|
if self.msgtype and self.msgtype not in MATRIX_MESSAGE_TYPES:
|
||||||
msg = 'The msgtype specified ({}) is invalid.'.format(msgtype)
|
msg = 'The msgtype specified ({}) is invalid.'.format(msgtype)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
@ -411,7 +410,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not hasattr(self, '_re_slack_formatting_rules'):
|
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 = {
|
self._re_slack_formatting_map = {
|
||||||
# New lines must become the string version
|
# 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
|
# We can't join a room if we're not logged in
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not isinstance(room, six.string_types):
|
if not isinstance(room, str):
|
||||||
# Not a supported string
|
# Not a supported string
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -850,7 +849,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
# We can't create a room if we're not logged in
|
# We can't create a room if we're not logged in
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not isinstance(room, six.string_types):
|
if not isinstance(room, str):
|
||||||
# Not a supported string
|
# Not a supported string
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -930,7 +929,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
# We can't get a room id if we're not logged in
|
# We can't get a room id if we're not logged in
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not isinstance(room, six.string_types):
|
if not isinstance(room, str):
|
||||||
# Not a supported string
|
# Not a supported string
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1109,20 +1108,20 @@ class NotifyMatrix(NotifyBase):
|
||||||
# - https://bugs.python.org/issue29288
|
# - https://bugs.python.org/issue29288
|
||||||
#
|
#
|
||||||
# A ~similar~ issue can be identified here in the requests
|
# 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
|
# - https://github.com/kennethreitz/requests/issues/3578
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
# The actual exception is `ModuleNotFoundError` however ImportError
|
# 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
|
# than v3.6
|
||||||
|
|
||||||
# Python code that makes early calls to sys.exit() can cause
|
# 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
|
# Python, this causes the `sys` library to no longer be
|
||||||
# available. The stack overflow also goes on to suggest that
|
# 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.
|
# which is the case here.
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/67218341/\
|
# https://stackoverflow.com/questions/67218341/\
|
||||||
|
@ -1134,7 +1133,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
# /1481488/what-is-the-del-method-and-how-do-i-call-it
|
# /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)
|
# 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.
|
# the end user if we don't have to.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
# - swap http with mmost
|
# - swap http with mmost
|
||||||
# - drop /hooks/ reference
|
# - drop /hooks/ reference
|
||||||
|
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
|
@ -156,7 +155,7 @@ class NotifyMattermost(NotifyBase):
|
||||||
|
|
||||||
# our full path
|
# our full path
|
||||||
self.fullpath = '' if not isinstance(
|
self.fullpath = '' if not isinstance(
|
||||||
fullpath, six.string_types) else fullpath.strip()
|
fullpath, str) else fullpath.strip()
|
||||||
|
|
||||||
# Authorization Token (associated with project)
|
# Authorization Token (associated with project)
|
||||||
self.token = validate_regex(token)
|
self.token = validate_regex(token)
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
# notica://abc123
|
# notica://abc123
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
|
@ -47,7 +46,7 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class NoticaMode(object):
|
class NoticaMode:
|
||||||
"""
|
"""
|
||||||
Tracks if we're accessing the notica upstream server or a locally hosted
|
Tracks if we're accessing the notica upstream server or a locally hosted
|
||||||
one.
|
one.
|
||||||
|
@ -176,7 +175,7 @@ class NotifyNotica(NotifyBase):
|
||||||
|
|
||||||
# prepare our fullpath
|
# prepare our fullpath
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
|
|
|
@ -48,8 +48,8 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class NotificoFormat(object):
|
class NotificoFormat:
|
||||||
# Resets all formating
|
# Resets all formatting
|
||||||
Reset = '\x0F'
|
Reset = '\x0F'
|
||||||
|
|
||||||
# Formatting
|
# Formatting
|
||||||
|
@ -59,7 +59,7 @@ class NotificoFormat(object):
|
||||||
BGSwap = '\x16'
|
BGSwap = '\x16'
|
||||||
|
|
||||||
|
|
||||||
class NotificoColor(object):
|
class NotificoColor:
|
||||||
# Resets Color
|
# Resets Color
|
||||||
Reset = '\x03'
|
Reset = '\x03'
|
||||||
|
|
||||||
|
@ -248,13 +248,13 @@ class NotifyNotifico(NotifyBase):
|
||||||
if self.color:
|
if self.color:
|
||||||
# Colors were specified, make sure we capture and correctly
|
# Colors were specified, make sure we capture and correctly
|
||||||
# allow them to exist inline in the message
|
# allow them to exist inline in the message
|
||||||
# \g<1> is less ambigious than \1
|
# \g<1> is less ambiguous than \1
|
||||||
body = re.sub(r'\\x03(\d{0,2})', '\x03\g<1>', body)
|
body = re.sub(r'\\x03(\d{0,2})', r'\\x03\g<1>', body)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# no colors specified, make sure we strip out any colors found
|
# no colors specified, make sure we strip out any colors found
|
||||||
# to make the string read-able
|
# 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
|
# Prepare our payload
|
||||||
payload = {
|
payload = {
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
# ntfy://ntfy.local.domain/?priority=max
|
# ntfy://ntfy.local.domain/?priority=max
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import six
|
|
||||||
from json import loads
|
from json import loads
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
|
@ -50,7 +49,7 @@ from ..URLBase import PrivacyMode
|
||||||
from ..attachment.AttachBase import AttachBase
|
from ..attachment.AttachBase import AttachBase
|
||||||
|
|
||||||
|
|
||||||
class NtfyMode(object):
|
class NtfyMode:
|
||||||
"""
|
"""
|
||||||
Define ntfy Notification Modes
|
Define ntfy Notification Modes
|
||||||
"""
|
"""
|
||||||
|
@ -67,7 +66,7 @@ NTFY_MODES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NtfyPriority(object):
|
class NtfyPriority:
|
||||||
"""
|
"""
|
||||||
Ntfy Priority Definitions
|
Ntfy Priority Definitions
|
||||||
"""
|
"""
|
||||||
|
@ -247,7 +246,7 @@ class NotifyNtfy(NotifyBase):
|
||||||
|
|
||||||
# Prepare our mode
|
# Prepare our mode
|
||||||
self.mode = mode.strip().lower() \
|
self.mode = mode.strip().lower() \
|
||||||
if isinstance(mode, six.string_types) \
|
if isinstance(mode, str) \
|
||||||
else self.template_args['mode']['default']
|
else self.template_args['mode']['default']
|
||||||
|
|
||||||
if self.mode not in NTFY_MODES:
|
if self.mode not in NTFY_MODES:
|
||||||
|
|
|
@ -74,7 +74,7 @@ OPSGENIE_CATEGORIES = (
|
||||||
|
|
||||||
|
|
||||||
# Regions
|
# Regions
|
||||||
class OpsgenieRegion(object):
|
class OpsgenieRegion:
|
||||||
US = 'us'
|
US = 'us'
|
||||||
EU = 'eu'
|
EU = 'eu'
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ OPSGENIE_REGIONS = (
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class OpsgeniePriority(object):
|
class OpsgeniePriority:
|
||||||
LOW = 1
|
LOW = 1
|
||||||
MODERATE = 2
|
MODERATE = 2
|
||||||
NORMAL = 3
|
NORMAL = 3
|
||||||
|
|
|
@ -40,7 +40,7 @@ from ..utils import parse_bool
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class PagerDutySeverity(object):
|
class PagerDutySeverity:
|
||||||
"""
|
"""
|
||||||
Defines the Pager Duty Severity Levels
|
Defines the Pager Duty Severity Levels
|
||||||
"""
|
"""
|
||||||
|
@ -63,7 +63,7 @@ PAGERDUTY_SEVERITY_MAP = {
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class PagerDutyRegion(object):
|
class PagerDutyRegion:
|
||||||
US = 'us'
|
US = 'us'
|
||||||
EU = 'eu'
|
EU = 'eu'
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
# Official API reference: https://developer.gitter.im/docs/user-resource
|
# Official API reference: https://developer.gitter.im/docs/user-resource
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class ParsePlatformDevice(object):
|
class ParsePlatformDevice:
|
||||||
# All Devices
|
# All Devices
|
||||||
ALL = 'all'
|
ALL = 'all'
|
||||||
|
|
||||||
|
@ -134,7 +133,7 @@ class NotifyParsePlatform(NotifyBase):
|
||||||
super(NotifyParsePlatform, self).__init__(**kwargs)
|
super(NotifyParsePlatform, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = '/'
|
self.fullpath = '/'
|
||||||
|
|
||||||
# Application ID
|
# Application ID
|
||||||
|
|
|
@ -32,7 +32,7 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class ProwlPriority(object):
|
class ProwlPriority:
|
||||||
LOW = -2
|
LOW = -2
|
||||||
MODERATE = -1
|
MODERATE = -1
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
|
|
|
@ -23,8 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
# We use io because it allows us to test the open() call
|
|
||||||
import io
|
|
||||||
import base64
|
import base64
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
|
@ -36,7 +34,7 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class PushSaferSound(object):
|
class PushSaferSound:
|
||||||
"""
|
"""
|
||||||
Defines all of the supported PushSafe sounds
|
Defines all of the supported PushSafe sounds
|
||||||
"""
|
"""
|
||||||
|
@ -248,7 +246,7 @@ PUSHSAFER_SOUND_MAP = {
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class PushSaferPriority(object):
|
class PushSaferPriority:
|
||||||
LOW = -2
|
LOW = -2
|
||||||
MODERATE = -1
|
MODERATE = -1
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
|
@ -282,7 +280,7 @@ DEFAULT_PRIORITY = "normal"
|
||||||
|
|
||||||
|
|
||||||
# Vibrations
|
# Vibrations
|
||||||
class PushSaferVibration(object):
|
class PushSaferVibration:
|
||||||
"""
|
"""
|
||||||
Defines the acceptable vibration settings for notification
|
Defines the acceptable vibration settings for notification
|
||||||
"""
|
"""
|
||||||
|
@ -565,7 +563,7 @@ class NotifyPushSafer(NotifyBase):
|
||||||
attachment.url(privacy=True)))
|
attachment.url(privacy=True)))
|
||||||
|
|
||||||
try:
|
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
|
# Output must be in a DataURL format (that's what
|
||||||
# PushSafer calls it):
|
# PushSafer calls it):
|
||||||
attachment = (
|
attachment = (
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
|
@ -44,7 +43,7 @@ VALIDATE_DEVICE = re.compile(r'^[a-z0-9_]{1,25}$', re.I)
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class PushoverPriority(object):
|
class PushoverPriority:
|
||||||
LOW = -2
|
LOW = -2
|
||||||
MODERATE = -1
|
MODERATE = -1
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
|
@ -53,7 +52,7 @@ class PushoverPriority(object):
|
||||||
|
|
||||||
|
|
||||||
# Sounds
|
# Sounds
|
||||||
class PushoverSound(object):
|
class PushoverSound:
|
||||||
PUSHOVER = 'pushover'
|
PUSHOVER = 'pushover'
|
||||||
BIKE = 'bike'
|
BIKE = 'bike'
|
||||||
BUGLE = 'bugle'
|
BUGLE = 'bugle'
|
||||||
|
@ -280,7 +279,7 @@ class NotifyPushover(NotifyBase):
|
||||||
|
|
||||||
# Setup our sound
|
# Setup our sound
|
||||||
self.sound = NotifyPushover.default_pushover_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:
|
if self.sound and self.sound not in PUSHOVER_SOUNDS:
|
||||||
msg = 'The sound specified ({}) is invalid.'.format(sound)
|
msg = 'The sound specified ({}) is invalid.'.format(sound)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
|
@ -44,7 +44,6 @@
|
||||||
# - https://www.reddit.com/dev/api/
|
# - https://www.reddit.com/dev/api/
|
||||||
# - https://www.reddit.com/dev/api/#POST_api_submit
|
# - https://www.reddit.com/dev/api/#POST_api_submit
|
||||||
# - https://github.com/reddit-archive/reddit/wiki/API
|
# - https://github.com/reddit-archive/reddit/wiki/API
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -66,7 +65,7 @@ REDDIT_HTTP_ERROR_MAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RedditMessageKind(object):
|
class RedditMessageKind:
|
||||||
"""
|
"""
|
||||||
Define the kinds of messages supported
|
Define the kinds of messages supported
|
||||||
"""
|
"""
|
||||||
|
@ -271,7 +270,7 @@ class NotifyReddit(NotifyBase):
|
||||||
self.__access_token_expiry = datetime.utcnow()
|
self.__access_token_expiry = datetime.utcnow()
|
||||||
|
|
||||||
self.kind = kind.strip().lower() \
|
self.kind = kind.strip().lower() \
|
||||||
if isinstance(kind, six.string_types) \
|
if isinstance(kind, str) \
|
||||||
else self.template_args['kind']['default']
|
else self.template_args['kind']['default']
|
||||||
|
|
||||||
if self.kind not in REDDIT_MESSAGE_KINDS:
|
if self.kind not in REDDIT_MESSAGE_KINDS:
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
@ -54,7 +53,7 @@ RC_HTTP_ERROR_MAP = {
|
||||||
LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
||||||
|
|
||||||
|
|
||||||
class RocketChatAuthMode(object):
|
class RocketChatAuthMode:
|
||||||
"""
|
"""
|
||||||
The Chat Authentication mode is detected
|
The Chat Authentication mode is detected
|
||||||
"""
|
"""
|
||||||
|
@ -218,7 +217,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
|
|
||||||
# Authentication mode
|
# Authentication mode
|
||||||
self.mode = None \
|
self.mode = None \
|
||||||
if not isinstance(mode, six.string_types) \
|
if not isinstance(mode, str) \
|
||||||
else mode.lower()
|
else mode.lower()
|
||||||
|
|
||||||
if self.mode and self.mode not in ROCKETCHAT_AUTH_MODES:
|
if self.mode and self.mode not in ROCKETCHAT_AUTH_MODES:
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
# These are important <---^----------------------------------------^
|
# These are important <---^----------------------------------------^
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class RyverWebhookMode(object):
|
class RyverWebhookMode:
|
||||||
"""
|
"""
|
||||||
Ryver supports to webhook modes
|
Ryver supports to webhook modes
|
||||||
"""
|
"""
|
||||||
|
@ -152,7 +151,7 @@ class NotifyRyver(NotifyBase):
|
||||||
|
|
||||||
# Store our webhook mode
|
# Store our webhook mode
|
||||||
self.mode = None \
|
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:
|
if self.mode not in RYVER_WEBHOOK_MODES:
|
||||||
msg = 'The Ryver webhook mode specified ({}) is invalid.' \
|
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.mime.multipart import MIMEMultipart
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
try:
|
from urllib.parse import quote
|
||||||
# Python v3.x
|
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python v2.x
|
|
||||||
from urllib import quote
|
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
|
@ -395,7 +389,6 @@ class NotifySES(NotifyBase):
|
||||||
# Strip target out of bcc list if in To
|
# Strip target out of bcc list if in To
|
||||||
bcc = (self.bcc - set([to_addr]))
|
bcc = (self.bcc - set([to_addr]))
|
||||||
|
|
||||||
try:
|
|
||||||
# Format our cc addresses to support the Name field
|
# Format our cc addresses to support the Name field
|
||||||
cc = [formataddr(
|
cc = [formataddr(
|
||||||
(self.names.get(addr, False), addr), charset='utf-8')
|
(self.names.get(addr, False), addr), charset='utf-8')
|
||||||
|
@ -406,16 +399,6 @@ class NotifySES(NotifyBase):
|
||||||
(self.names.get(addr, False), addr), charset='utf-8')
|
(self.names.get(addr, False), addr), charset='utf-8')
|
||||||
for addr in bcc]
|
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]
|
|
||||||
|
|
||||||
self.logger.debug('Email From: {} <{}>'.format(
|
self.logger.debug('Email From: {} <{}>'.format(
|
||||||
quote(reply_to[0], ' '),
|
quote(reply_to[0], ' '),
|
||||||
quote(reply_to[1], '@ ')))
|
quote(reply_to[1], '@ ')))
|
||||||
|
@ -436,23 +419,14 @@ class NotifySES(NotifyBase):
|
||||||
# Create a Multipart container if there is an attachment
|
# Create a Multipart container if there is an attachment
|
||||||
base = MIMEMultipart() if attach else content
|
base = MIMEMultipart() if attach else content
|
||||||
|
|
||||||
|
# TODO: Deduplicate with `NotifyEmail`?
|
||||||
base['Subject'] = Header(title, 'utf-8')
|
base['Subject'] = Header(title, 'utf-8')
|
||||||
try:
|
|
||||||
base['From'] = formataddr(
|
base['From'] = formataddr(
|
||||||
(from_name if from_name else False, self.from_addr),
|
(from_name if from_name else False, self.from_addr),
|
||||||
charset='utf-8')
|
charset='utf-8')
|
||||||
base['To'] = formataddr((to_name, to_addr), charset='utf-8')
|
base['To'] = formataddr((to_name, to_addr), charset='utf-8')
|
||||||
if reply_to[1] != self.from_addr:
|
if reply_to[1] != self.from_addr:
|
||||||
base['Reply-To'] = formataddr(reply_to, charset='utf-8')
|
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['Cc'] = ','.join(cc)
|
base['Cc'] = ','.join(cc)
|
||||||
base['Date'] = \
|
base['Date'] = \
|
||||||
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||||
|
|
|
@ -47,7 +47,7 @@ CONTACT_REGEX = re.compile(
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class SMSEaglePriority(object):
|
class SMSEaglePriority:
|
||||||
NORMAL = 0
|
NORMAL = 0
|
||||||
HIGH = 1
|
HIGH = 1
|
||||||
|
|
||||||
|
|
|
@ -315,18 +315,10 @@ class NotifySMTP2Go(NotifyBase):
|
||||||
self.logger.debug('I/O Exception: %s' % str(e))
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
|
||||||
sender = formataddr(
|
sender = formataddr(
|
||||||
(self.from_name if self.from_name else False,
|
(self.from_name if self.from_name else False,
|
||||||
self.from_addr), charset='utf-8')
|
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))
|
|
||||||
|
|
||||||
# Prepare our payload
|
# Prepare our payload
|
||||||
payload = {
|
payload = {
|
||||||
# API Key
|
# API Key
|
||||||
|
@ -369,34 +361,18 @@ class NotifySMTP2Go(NotifyBase):
|
||||||
# Strip target out of bcc list if in To
|
# Strip target out of bcc list if in To
|
||||||
bcc = (bcc - set([to_addr[1]]))
|
bcc = (bcc - set([to_addr[1]]))
|
||||||
|
|
||||||
try:
|
# Prepare our `to`
|
||||||
# Prepare our to
|
|
||||||
to.append(formataddr(to_addr, charset='utf-8'))
|
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
|
# Prepare our To
|
||||||
payload['to'] = to
|
payload['to'] = to
|
||||||
|
|
||||||
if cc:
|
if cc:
|
||||||
try:
|
|
||||||
# Format our cc addresses to support the Name field
|
# Format our cc addresses to support the Name field
|
||||||
payload['cc'] = [formataddr(
|
payload['cc'] = [formataddr(
|
||||||
(self.names.get(addr, False), addr), charset='utf-8')
|
(self.names.get(addr, False), addr), charset='utf-8')
|
||||||
for addr in cc]
|
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 bcc addresses to support the Name field
|
# Format our bcc addresses to support the Name field
|
||||||
if bcc:
|
if bcc:
|
||||||
# set our bcc variable (convert to list first so it's
|
# set our bcc variable (convert to list first so it's
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
# from). Activated phone numbers can be found on your dashboard here:
|
# from). Activated phone numbers can be found on your dashboard here:
|
||||||
# - https://dashboard.sinch.com/numbers/your-numbers/numbers
|
# - https://dashboard.sinch.com/numbers/your-numbers/numbers
|
||||||
#
|
#
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class SinchRegion(object):
|
class SinchRegion:
|
||||||
"""
|
"""
|
||||||
Defines the Sinch Server Regions
|
Defines the Sinch Server Regions
|
||||||
"""
|
"""
|
||||||
|
@ -192,7 +191,7 @@ class NotifySinch(NotifyBase):
|
||||||
|
|
||||||
# Setup our region
|
# Setup our region
|
||||||
self.region = self.template_args['region']['default'] \
|
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:
|
if self.region and self.region not in SINCH_REGIONS:
|
||||||
msg = 'The region specified ({}) is invalid.'.format(region)
|
msg = 'The region specified ({}) is invalid.'.format(region)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
|
@ -94,7 +94,7 @@ SLACK_HTTP_ERROR_MAP = {
|
||||||
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
|
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
|
||||||
|
|
||||||
|
|
||||||
class SlackMode(object):
|
class SlackMode:
|
||||||
"""
|
"""
|
||||||
Tracks the mode of which we're using Slack
|
Tracks the mode of which we're using Slack
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -80,7 +80,7 @@ SPARKPOST_HTTP_ERROR_MAP = {
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
class SparkPostRegion(object):
|
class SparkPostRegion:
|
||||||
US = 'us'
|
US = 'us'
|
||||||
EU = 'eu'
|
EU = 'eu'
|
||||||
|
|
||||||
|
@ -503,14 +503,9 @@ class NotifySparkPost(NotifyBase):
|
||||||
# Send in batches if identified to do so
|
# Send in batches if identified to do so
|
||||||
batch_size = 1 if not self.batch else self.default_batch_size
|
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,
|
reply_to = formataddr((self.from_name if self.from_name else False,
|
||||||
self.from_addr), charset='utf-8')
|
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))
|
|
||||||
payload = {
|
payload = {
|
||||||
"options": {
|
"options": {
|
||||||
# When set to True, an image is included with the email which
|
# When set to True, an image is included with the email which
|
||||||
|
|
|
@ -42,7 +42,7 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
# calls
|
# calls
|
||||||
class StrmlabsCall(object):
|
class StrmlabsCall:
|
||||||
ALERT = 'ALERTS'
|
ALERT = 'ALERTS'
|
||||||
DONATION = 'DONATIONS'
|
DONATION = 'DONATIONS'
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ STRMLABS_CALLS = (
|
||||||
|
|
||||||
|
|
||||||
# alerts
|
# alerts
|
||||||
class StrmlabsAlert(object):
|
class StrmlabsAlert:
|
||||||
FOLLOW = 'follow'
|
FOLLOW = 'follow'
|
||||||
SUBSCRIPTION = 'subscription'
|
SUBSCRIPTION = 'subscription'
|
||||||
DONATION = 'donation'
|
DONATION = 'donation'
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
import syslog
|
import syslog
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
@ -101,7 +100,7 @@ SYSLOG_FACILITY_RMAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SyslogMode(object):
|
class SyslogMode:
|
||||||
# A local query
|
# A local query
|
||||||
LOCAL = "local"
|
LOCAL = "local"
|
||||||
|
|
||||||
|
@ -217,7 +216,7 @@ class NotifySyslog(NotifyBase):
|
||||||
self.template_tokens['facility']['default']]
|
self.template_tokens['facility']['default']]
|
||||||
|
|
||||||
self.mode = self.template_args['mode']['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:
|
if self.mode not in SYSLOG_MODES:
|
||||||
msg = 'The mode specified ({}) is invalid.'.format(mode)
|
msg = 'The mode specified ({}) is invalid.'.format(mode)
|
||||||
|
|
|
@ -785,7 +785,7 @@ class NotifyTwist(NotifyBase):
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""
|
"""
|
||||||
Deconstructor
|
Destructor
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.logout()
|
self.logout()
|
||||||
|
@ -808,14 +808,14 @@ class NotifyTwist(NotifyBase):
|
||||||
|
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
# The actual exception is `ModuleNotFoundError` however ImportError
|
# 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
|
# than v3.6
|
||||||
|
|
||||||
# Python code that makes early calls to sys.exit() can cause
|
# 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
|
# Python, this causes the `sys` library to no longer be
|
||||||
# available. The stack overflow also goes on to suggest that
|
# 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.
|
# which is the case here.
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/67218341/\
|
# https://stackoverflow.com/questions/67218341/\
|
||||||
|
@ -827,6 +827,6 @@ class NotifyTwist(NotifyBase):
|
||||||
# /1481488/what-is-the-del-method-and-how-do-i-call-it
|
# /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)
|
# 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.
|
# the end user if we don't have to.
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
# See https://developer.twitter.com/en/docs/direct-messages/\
|
# See https://developer.twitter.com/en/docs/direct-messages/\
|
||||||
# sending-and-receiving/api-reference/new-event.html
|
# sending-and-receiving/api-reference/new-event.html
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
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)
|
IS_USER = re.compile(r'^\s*@?(?P<user>[A-Z0-9_]+)$', re.I)
|
||||||
|
|
||||||
|
|
||||||
class TwitterMessageMode(object):
|
class TwitterMessageMode:
|
||||||
"""
|
"""
|
||||||
Twitter Message Mode
|
Twitter Message Mode
|
||||||
"""
|
"""
|
||||||
|
@ -223,7 +222,7 @@ class NotifyTwitter(NotifyBase):
|
||||||
|
|
||||||
# Store our webhook mode
|
# Store our webhook mode
|
||||||
self.mode = None \
|
self.mode = None \
|
||||||
if not isinstance(mode, six.string_types) else mode.lower()
|
if not isinstance(mode, str) else mode.lower()
|
||||||
|
|
||||||
# Set Cache Flag
|
# Set Cache Flag
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
@ -157,11 +156,11 @@ class NotifyXML(NotifyBase):
|
||||||
</soapenv:Envelope>"""
|
</soapenv:Envelope>"""
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
self.fullpath = kwargs.get('fullpath')
|
||||||
if not isinstance(self.fullpath, six.string_types):
|
if not isinstance(self.fullpath, str):
|
||||||
self.fullpath = ''
|
self.fullpath = ''
|
||||||
|
|
||||||
self.method = self.template_args['method']['default'] \
|
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:
|
if self.method not in METHODS:
|
||||||
msg = 'The method specified ({}) is invalid.'.format(method)
|
msg = 'The method specified ({}) is invalid.'.format(method)
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
import re
|
import re
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
@ -120,28 +119,8 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.plugins'):
|
||||||
globals()[plugin_name] = plugin
|
globals()[plugin_name] = plugin
|
||||||
|
|
||||||
fn = getattr(plugin, 'schemas', None)
|
fn = getattr(plugin, 'schemas', None)
|
||||||
try:
|
|
||||||
schemas = set([]) if not callable(fn) else fn(plugin)
|
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)
|
|
||||||
|
|
||||||
# map our schema to our plugin
|
# map our schema to our plugin
|
||||||
for schema in schemas:
|
for schema in schemas:
|
||||||
if schema in common.NOTIFY_SCHEMA_MAP:
|
if schema in common.NOTIFY_SCHEMA_MAP:
|
||||||
|
@ -232,7 +211,7 @@ def _sanitize_token(tokens, default_delimiter):
|
||||||
|
|
||||||
if 'regex' in tokens[key]:
|
if 'regex' in tokens[key]:
|
||||||
# Verify that we are a tuple; convert strings to tuples
|
# 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
|
# Default tuple setup
|
||||||
tokens[key]['regex'] = \
|
tokens[key]['regex'] = \
|
||||||
(tokens[key]['regex'], None)
|
(tokens[key]['regex'], None)
|
||||||
|
@ -473,7 +452,7 @@ def requirements(plugin):
|
||||||
|
|
||||||
# Get our required packages
|
# Get our required packages
|
||||||
_req_packages = plugin.requirements.get('packages_required')
|
_req_packages = plugin.requirements.get('packages_required')
|
||||||
if isinstance(_req_packages, six.string_types):
|
if isinstance(_req_packages, str):
|
||||||
# Convert to list
|
# Convert to list
|
||||||
_req_packages = [_req_packages]
|
_req_packages = [_req_packages]
|
||||||
|
|
||||||
|
@ -485,7 +464,7 @@ def requirements(plugin):
|
||||||
|
|
||||||
# Get our recommended packages
|
# Get our recommended packages
|
||||||
_opt_packages = plugin.requirements.get('packages_recommended')
|
_opt_packages = plugin.requirements.get('packages_recommended')
|
||||||
if isinstance(_opt_packages, six.string_types):
|
if isinstance(_opt_packages, str):
|
||||||
# Convert to list
|
# Convert to list
|
||||||
_opt_packages = [_opt_packages]
|
_opt_packages = [_opt_packages]
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,7 @@ ASYNCIO_RUN_SUPPORT = \
|
||||||
(sys.version_info.major == 3 and sys.version_info.minor >= 7)
|
(sys.version_info.major == 3 and sys.version_info.minor >= 7)
|
||||||
|
|
||||||
|
|
||||||
# async reference produces a SyntaxError (E999) in Python v2.7
|
async def notify(coroutines):
|
||||||
# For this reason we turn on the noqa flag
|
|
||||||
async def notify(coroutines): # noqa: E999
|
|
||||||
"""
|
"""
|
||||||
An async wrapper to the AsyncNotifyBase.async_notify() calls allowing us
|
An async wrapper to the AsyncNotifyBase.async_notify() calls allowing us
|
||||||
to call gather() and collect the responses
|
to call gather() and collect the responses
|
||||||
|
@ -98,7 +96,7 @@ def tosync(cor, debug=False):
|
||||||
return loop.run_until_complete(cor)
|
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.
|
Create a coroutine that, when run, returns the provided value.
|
||||||
"""
|
"""
|
||||||
|
@ -106,7 +104,7 @@ async def toasyncwrapvalue(v): # noqa: E999
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
async def toasyncwrap(fn): # noqa: E999
|
async def toasyncwrap(fn):
|
||||||
"""
|
"""
|
||||||
Create a coroutine that, when run, executes the provided function.
|
Create a coroutine that, when run, executes the provided function.
|
||||||
"""
|
"""
|
||||||
|
@ -119,7 +117,7 @@ class AsyncNotifyBase(URLBase):
|
||||||
asyncio wrapper for the NotifyBase object
|
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
|
Async Notification Wrapper
|
||||||
"""
|
"""
|
||||||
|
@ -131,11 +129,11 @@ class AsyncNotifyBase(URLBase):
|
||||||
None, partial(self.notify, *args, **kwargs))
|
None, partial(self.notify, *args, **kwargs))
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# These our our internally thrown notifications
|
# These are our internally thrown notifications
|
||||||
pass
|
pass
|
||||||
|
|
||||||
except Exception:
|
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.
|
# just because one of our plugins has a bug in it.
|
||||||
logger.exception("Notification Exception")
|
logger.exception("Notification Exception")
|
||||||
|
|
||||||
|
|
107
apprise/utils.py
107
apprise/utils.py
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import contextlib
|
import contextlib
|
||||||
|
@ -36,39 +35,15 @@ from functools import reduce
|
||||||
from . import common
|
from . import common
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
|
|
||||||
try:
|
from urllib.parse import unquote
|
||||||
# Python 2.7
|
from urllib.parse import quote
|
||||||
from urllib import unquote
|
from urllib.parse import urlparse
|
||||||
from urllib import quote
|
from urllib.parse import urlencode as _urlencode
|
||||||
from urlparse import urlparse
|
|
||||||
from urllib 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:
|
def import_module(path, name):
|
||||||
logger.debug(
|
|
||||||
'Custom module exception raised from %s (name=%s) %s',
|
|
||||||
path, name, str(e))
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 3.5+
|
|
||||||
from urllib.parse import unquote
|
|
||||||
from urllib.parse import quote
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from urllib.parse import urlencode as _urlencode
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
def import_module(path, name):
|
|
||||||
"""
|
"""
|
||||||
Load our module based on path
|
Load our module based on path
|
||||||
"""
|
"""
|
||||||
|
@ -94,6 +69,7 @@ except ImportError:
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
# Hash of all paths previously scanned so we don't waste effort/overhead doing
|
# Hash of all paths previously scanned so we don't waste effort/overhead doing
|
||||||
# it again
|
# it again
|
||||||
PATHS_PREVIOUSLY_SCANNED = set()
|
PATHS_PREVIOUSLY_SCANNED = set()
|
||||||
|
@ -226,7 +202,7 @@ UUID4_RE = re.compile(
|
||||||
REGEX_VALIDATE_LOOKUP = {}
|
REGEX_VALIDATE_LOOKUP = {}
|
||||||
|
|
||||||
|
|
||||||
class TemplateType(object):
|
class TemplateType:
|
||||||
"""
|
"""
|
||||||
Defines the different template types we can perform parsing on
|
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
|
# Simple error checking
|
||||||
return None
|
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
|
# Re-assemble cleaned up version of the url
|
||||||
result['url'] = '%s://' % result['schema']
|
result['url'] = '%s://' % result['schema']
|
||||||
if isinstance(result.get('user'), six.string_types):
|
if isinstance(result.get('user'), str):
|
||||||
result['url'] += result['user']
|
result['url'] += result['user']
|
||||||
|
|
||||||
if isinstance(result.get('password'), six.string_types):
|
if isinstance(result.get('password'), str):
|
||||||
result['url'] += ':%s@' % result['password']
|
result['url'] += ':%s@' % result['password']
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -900,7 +876,7 @@ def parse_bool(arg, default=False):
|
||||||
If the content could not be parsed, then the default is returned.
|
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
|
# no = no - False
|
||||||
# of = short for off - False
|
# of = short for off - False
|
||||||
# 0 = int for False
|
# 0 = int for False
|
||||||
|
@ -930,20 +906,15 @@ def parse_bool(arg, default=False):
|
||||||
return bool(arg)
|
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
|
Takes a string containing phone numbers separated by comma's and/or spaces
|
||||||
and returns a list.
|
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 = []
|
result = []
|
||||||
for arg in args:
|
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)
|
_result = PHONE_NO_DETECTION_RE.findall(arg)
|
||||||
if _result:
|
if _result:
|
||||||
result += _result
|
result += _result
|
||||||
|
@ -967,20 +938,15 @@ def parse_phone_no(*args, **kwargs):
|
||||||
return result
|
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
|
Takes a string containing ham radio call signs separated by
|
||||||
comma and/or spacesand returns a list.
|
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 = []
|
result = []
|
||||||
for arg in args:
|
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)
|
_result = CALL_SIGN_DETECTION_RE.findall(arg)
|
||||||
if _result:
|
if _result:
|
||||||
result += _result
|
result += _result
|
||||||
|
@ -1004,20 +970,15 @@ def parse_call_sign(*args, **kwargs):
|
||||||
return result
|
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
|
Takes a string containing emails separated by comma's and/or spaces and
|
||||||
returns a list.
|
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 = []
|
result = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if isinstance(arg, six.string_types) and arg:
|
if isinstance(arg, str) and arg:
|
||||||
_result = EMAIL_DETECTION_RE.findall(arg)
|
_result = EMAIL_DETECTION_RE.findall(arg)
|
||||||
if _result:
|
if _result:
|
||||||
result += _result
|
result += _result
|
||||||
|
@ -1040,20 +1001,15 @@ def parse_emails(*args, **kwargs):
|
||||||
return result
|
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
|
Takes a string containing URLs separated by comma's and/or spaces and
|
||||||
returns a list.
|
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 = []
|
result = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if isinstance(arg, six.string_types) and arg:
|
if isinstance(arg, str) and arg:
|
||||||
_result = URL_DETECTION_RE.findall(arg)
|
_result = URL_DETECTION_RE.findall(arg)
|
||||||
if _result:
|
if _result:
|
||||||
result += _result
|
result += _result
|
||||||
|
@ -1140,16 +1096,10 @@ def urlencode(query, doseq=False, safe='', encoding=None, errors=None):
|
||||||
"""
|
"""
|
||||||
# Tidy query by eliminating any records set to None
|
# Tidy query by eliminating any records set to None
|
||||||
_query = {k: v for (k, v) in query.items() if v is not None}
|
_query = {k: v for (k, v) in query.items() if v is not None}
|
||||||
try:
|
|
||||||
# Python v3.x
|
|
||||||
return _urlencode(
|
return _urlencode(
|
||||||
_query, doseq=doseq, safe=safe, encoding=encoding,
|
_query, doseq=doseq, safe=safe, encoding=encoding,
|
||||||
errors=errors)
|
errors=errors)
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
# Python v2.7
|
|
||||||
return _urlencode(_query)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_list(*args):
|
def parse_list(*args):
|
||||||
"""
|
"""
|
||||||
|
@ -1174,7 +1124,7 @@ def parse_list(*args):
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if isinstance(arg, six.string_types):
|
if isinstance(arg, str):
|
||||||
result += re.split(STRING_DELIMITERS, arg)
|
result += re.split(STRING_DELIMITERS, arg)
|
||||||
|
|
||||||
elif isinstance(arg, (set, list, tuple)):
|
elif isinstance(arg, (set, list, tuple)):
|
||||||
|
@ -1183,9 +1133,10 @@ def parse_list(*args):
|
||||||
#
|
#
|
||||||
# filter() eliminates any empty entries
|
# 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
|
# a list, we need to change it into a list object to remain compatible with
|
||||||
# both distribution types.
|
# both distribution types.
|
||||||
|
# TODO: Review after dropping support for Python 2.
|
||||||
return sorted([x for x in filter(bool, list(set(result)))])
|
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.
|
to all specified logic searches.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(logic, six.string_types):
|
if isinstance(logic, str):
|
||||||
# Update our logic to support our delimiters
|
# Update our logic to support our delimiters
|
||||||
logic = set(parse_list(logic))
|
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
|
# Every entry here will be or'ed with the next
|
||||||
for entry in logic:
|
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
|
# Garbage entry in our logic found
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -1300,7 +1251,7 @@ def validate_regex(value, regex=r'[^\s]+', flags=re.I, strip=True, fmt=None):
|
||||||
'x': re.X,
|
'x': re.X,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(flags, six.string_types):
|
if isinstance(flags, str):
|
||||||
# Convert a string of regular expression flags into their
|
# Convert a string of regular expression flags into their
|
||||||
# respected integer (expected) Python values and perform
|
# respected integer (expected) Python values and perform
|
||||||
# a bit-wise or on each match found:
|
# 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
|
reached, then content is considered secret
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Variance(object):
|
class Variance:
|
||||||
"""
|
"""
|
||||||
A Simple List of Possible Character Variances
|
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)
|
# A Numerical Character (1234... etc)
|
||||||
NUMERIC = 'n'
|
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
|
# not a password if it's not something we even support
|
||||||
return word
|
return word
|
||||||
|
|
||||||
|
@ -1594,7 +1545,7 @@ def module_detection(paths, cache=True):
|
||||||
module_re = re.compile(
|
module_re = re.compile(
|
||||||
r'^(?P<name>[_a-z0-9][a-z0-9._-]+)?(\.py)?$', re.I)
|
r'^(?P<name>[_a-z0-9][a-z0-9._-]+)?(\.py)?$', re.I)
|
||||||
|
|
||||||
if isinstance(paths, six.string_types):
|
if isinstance(paths, str):
|
||||||
paths = [paths, ]
|
paths = [paths, ]
|
||||||
|
|
||||||
if not paths or not isinstance(paths, (tuple, list)):
|
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
|
# First assume we might be in the ./bin directory
|
||||||
sys.path.insert(
|
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
|
# The user might have copied the apprise script back one directory
|
||||||
# so support this too..
|
# so support this too..
|
||||||
sys.path.insert(
|
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
|
# We can also use the current directory we're standing in as a last
|
||||||
# resort
|
# resort
|
||||||
sys.path.insert(0, join(getcwd())) # noqa
|
sys.path.insert(0, join(getcwd()))
|
||||||
|
|
||||||
# Apprise tool now importable
|
# Apprise tool now importable
|
||||||
from apprise.cli import main
|
from apprise.cli import main # noqa E402
|
||||||
import logging
|
import logging # noqa E402
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
coverage
|
coverage
|
||||||
flake8
|
flake8
|
||||||
mock; python_version=='2.7'
|
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
tox
|
tox
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
## Packaging
|
## 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
|
### RPM Based Packages
|
||||||
* [EPEL](https://fedoraproject.org/wiki/EPEL) based distributions are only supported if they are of v7 or higher. This includes:
|
* [EPEL](https://fedoraproject.org/wiki/EPEL) based distributions are only supported if they are of v8 or higher. This includes:
|
||||||
* Red Hat 7.x (or higher)
|
* Red Hat 8.x (or higher)
|
||||||
* CentOS 7.x (or higher)
|
* Scientific OS 8.x (or higher)
|
||||||
* Scientific OS 7.x (or higher)
|
* Oracle Linux 8.x (or higher)
|
||||||
* Oracle Linux 7.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:
|
Provided you are connected to the [EPEL repositories](https://fedoraproject.org/wiki/EPEL), the following will just work for you:
|
||||||
```bash
|
```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
|
# python3-apprise: contains all you need to develop with apprise
|
||||||
# apprise: provides the 'apprise' administrative tool
|
# apprise: provides the 'apprise' administrative tool
|
||||||
dnf install python3-apprise apprise
|
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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# 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}
|
%if 0%{?_module_build}
|
||||||
%bcond_with tests
|
%bcond_with tests
|
||||||
%else
|
%else
|
||||||
|
@ -36,10 +28,6 @@
|
||||||
%bcond_without tests
|
%bcond_without tests
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%if 0%{?rhel} && 0%{?rhel} <= 7
|
|
||||||
%global with_python3 0
|
|
||||||
%endif
|
|
||||||
|
|
||||||
%global pypi_name apprise
|
%global pypi_name apprise
|
||||||
|
|
||||||
%global common_description %{expand: \
|
%global common_description %{expand: \
|
||||||
|
@ -61,85 +49,30 @@ Teams}
|
||||||
|
|
||||||
Name: python-%{pypi_name}
|
Name: python-%{pypi_name}
|
||||||
Version: 1.0.0
|
Version: 1.0.0
|
||||||
Release: 2%{?dist}
|
Release: 3%{?dist}
|
||||||
Summary: A simple wrapper to many popular notification services used today
|
Summary: A simple wrapper to many popular notification services used today
|
||||||
License: MIT
|
License: MIT
|
||||||
URL: https://github.com/caronc/%{pypi_name}
|
URL: https://github.com/caronc/%{pypi_name}
|
||||||
Source0: %{url}/archive/v%{version}/%{pypi_name}-%{version}.tar.gz
|
Source0: %{url}/archive/v%{version}/%{pypi_name}-%{version}.tar.gz
|
||||||
# this patch allows version of requests that ships with RHEL v7 to
|
# RHEL/Rocky 8 ship with Click v6.7 which does not support the .stdout
|
||||||
# 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
|
|
||||||
# directive used in the unit testing. This patch just makes it so our package
|
# directive used in the unit testing. This patch just makes it so our package
|
||||||
# continues to be compatible with these linux distributions
|
# continues to be compatible with these linux distributions
|
||||||
Patch1: %{pypi_name}-click67-support.patch
|
Patch0: %{pypi_name}-click67-support.patch
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
%description %{common_description}
|
%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}
|
%package -n %{pypi_name}
|
||||||
Summary: Apprise CLI Tool
|
Summary: Apprise CLI Tool
|
||||||
|
|
||||||
%if 0%{?with_python3}
|
|
||||||
Requires: python%{python3_pkgversion}-click >= 5.0
|
Requires: python%{python3_pkgversion}-click >= 5.0
|
||||||
Requires: python%{python3_pkgversion}-%{pypi_name} = %{version}-%{release}
|
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}
|
%description -n %{pypi_name}
|
||||||
An accompanied CLI tool that can be used as part of Apprise
|
An accompanied CLI tool that can be used as part of Apprise
|
||||||
to issue notifications from the command line to you favorite
|
to issue notifications from the command line to you favorite
|
||||||
services.
|
services.
|
||||||
|
|
||||||
%if 0%{?with_python3}
|
|
||||||
%package -n python%{python3_pkgversion}-%{pypi_name}
|
%package -n python%{python3_pkgversion}-%{pypi_name}
|
||||||
Summary: A simple wrapper to many popular notification services used today
|
Summary: A simple wrapper to many popular notification services used today
|
||||||
%{?python_provide:%python_provide python%{python3_pkgversion}-%{pypi_name}}
|
%{?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}-setuptools
|
||||||
BuildRequires: python%{python3_pkgversion}-requests
|
BuildRequires: python%{python3_pkgversion}-requests
|
||||||
BuildRequires: python%{python3_pkgversion}-requests-oauthlib
|
BuildRequires: python%{python3_pkgversion}-requests-oauthlib
|
||||||
BuildRequires: python%{python3_pkgversion}-six
|
|
||||||
BuildRequires: python%{python3_pkgversion}-click >= 5.0
|
BuildRequires: python%{python3_pkgversion}-click >= 5.0
|
||||||
BuildRequires: python%{python3_pkgversion}-markdown
|
BuildRequires: python%{python3_pkgversion}-markdown
|
||||||
BuildRequires: python%{python3_pkgversion}-yaml
|
BuildRequires: python%{python3_pkgversion}-yaml
|
||||||
|
@ -156,7 +88,6 @@ BuildRequires: python%{python3_pkgversion}-babel
|
||||||
BuildRequires: python%{python3_pkgversion}-cryptography
|
BuildRequires: python%{python3_pkgversion}-cryptography
|
||||||
Requires: python%{python3_pkgversion}-requests
|
Requires: python%{python3_pkgversion}-requests
|
||||||
Requires: python%{python3_pkgversion}-requests-oauthlib
|
Requires: python%{python3_pkgversion}-requests-oauthlib
|
||||||
Requires: python%{python3_pkgversion}-six
|
|
||||||
Requires: python%{python3_pkgversion}-markdown
|
Requires: python%{python3_pkgversion}-markdown
|
||||||
Requires: python%{python3_pkgversion}-cryptography
|
Requires: python%{python3_pkgversion}-cryptography
|
||||||
Requires: python%{python3_pkgversion}-yaml
|
Requires: python%{python3_pkgversion}-yaml
|
||||||
|
@ -173,93 +104,52 @@ BuildRequires: python%{python3_pkgversion}-pytest-runner
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%description -n python%{python3_pkgversion}-%{pypi_name} %{common_description}
|
%description -n python%{python3_pkgversion}-%{pypi_name} %{common_description}
|
||||||
%endif
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%setup -q -n %{pypi_name}-%{version}
|
%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
|
%if 0%{?rhel} && 0%{?rhel} <= 8
|
||||||
# click v6.7 unit testing support
|
# Rocky/RHEL 8 click v6.7 unit testing support
|
||||||
%patch1 -p1
|
%patch0 -p1
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%if 0%{?rhel} >= 9
|
%if 0%{?rhel} >= 9
|
||||||
# Nothing to do under normal circumstances; this line here allows legacy
|
# Do nothing
|
||||||
# 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' {} \;
|
|
||||||
%else
|
%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 \
|
find test -type f -name '*.py' -exec \
|
||||||
sed -i -e 's|^from unittest import mock|import mock|g' {} \;
|
sed -i -e 's|^from unittest import mock|import mock|g' {} \;
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%build
|
%build
|
||||||
%if 0%{?with_python2}
|
|
||||||
%py2_build
|
|
||||||
%endif
|
|
||||||
%if 0%{?with_python3}
|
|
||||||
%py3_build
|
%py3_build
|
||||||
%endif
|
|
||||||
|
|
||||||
%install
|
%install
|
||||||
%if 0%{?with_python2}
|
|
||||||
%py2_install
|
|
||||||
%endif
|
|
||||||
%if 0%{?with_python3}
|
|
||||||
%py3_install
|
%py3_install
|
||||||
%endif
|
|
||||||
|
|
||||||
install -p -D -T -m 0644 packaging/man/%{pypi_name}.1 \
|
install -p -D -T -m 0644 packaging/man/%{pypi_name}.1 \
|
||||||
%{buildroot}%{_mandir}/man1/%{pypi_name}.1
|
%{buildroot}%{_mandir}/man1/%{pypi_name}.1
|
||||||
|
|
||||||
%if %{with tests}
|
%if %{with tests}
|
||||||
%check
|
%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}
|
LANG=C.UTF-8 PYTHONPATH=%{buildroot}%{python3_sitelib} py.test-%{python3_version}
|
||||||
%endif
|
%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}
|
%files -n python%{python3_pkgversion}-%{pypi_name}
|
||||||
%license LICENSE
|
%license LICENSE
|
||||||
%doc README.md
|
%doc README.md
|
||||||
%{python3_sitelib}/%{pypi_name}
|
%{python3_sitelib}/%{pypi_name}
|
||||||
%exclude %{python3_sitelib}/%{pypi_name}/cli.*
|
%exclude %{python3_sitelib}/%{pypi_name}/cli.*
|
||||||
%{python3_sitelib}/*.egg-info
|
%{python3_sitelib}/*.egg-info
|
||||||
%endif
|
|
||||||
|
|
||||||
%files -n %{pypi_name}
|
%files -n %{pypi_name}
|
||||||
%{_bindir}/%{pypi_name}
|
%{_bindir}/%{pypi_name}
|
||||||
%{_mandir}/man1/%{pypi_name}.1*
|
%{_mandir}/man1/%{pypi_name}.1*
|
||||||
|
|
||||||
%if 0%{?with_python3}
|
|
||||||
%{python3_sitelib}/%{pypi_name}/cli.*
|
%{python3_sitelib}/%{pypi_name}/cli.*
|
||||||
%endif
|
|
||||||
|
|
||||||
%if 0%{?with_python2}
|
|
||||||
%{python2_sitelib}/%{pypi_name}/cli.*
|
|
||||||
%endif
|
|
||||||
|
|
||||||
%changelog
|
%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
|
* Wed Aug 31 2022 Chris Caron <lead2gold@gmail.com> - 1.0.0-2
|
||||||
- Rebuilt for RHEL9 Support
|
- Rebuilt for RHEL9 Support
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
requests
|
requests
|
||||||
requests-oauthlib
|
requests-oauthlib
|
||||||
six
|
|
||||||
click >= 5.0
|
click >= 5.0
|
||||||
markdown
|
markdown
|
||||||
PyYAML
|
PyYAML
|
||||||
|
|
|
@ -21,7 +21,6 @@ python_files = test/test_*.py
|
||||||
norecursedirs=test/helpers
|
norecursedirs=test/helpers
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
once::Warning
|
once::Warning
|
||||||
strict = true
|
|
||||||
|
|
||||||
[extract_messages]
|
[extract_messages]
|
||||||
output-file = apprise/i18n/apprise.pot
|
output-file = apprise/i18n/apprise.pot
|
||||||
|
|
18
setup.py
18
setup.py
|
@ -27,13 +27,7 @@
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
try:
|
from setuptools import find_packages, setup
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
from distutils.core import setup
|
|
||||||
|
|
||||||
from setuptools import find_packages
|
|
||||||
|
|
||||||
cmdclass = {}
|
cmdclass = {}
|
||||||
try:
|
try:
|
||||||
|
@ -94,13 +88,19 @@ setup(
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2.7',
|
|
||||||
'Programming Language :: Python :: 3',
|
'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',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
'Topic :: Software Development :: Libraries :: Application Frameworks',
|
||||||
),
|
),
|
||||||
entry_points={'console_scripts': console_scripts},
|
entry_points={'console_scripts': console_scripts},
|
||||||
python_requires='>=2.7',
|
python_requires='>=3.6',
|
||||||
setup_requires=['babel', ],
|
setup_requires=['babel', ],
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,16 +27,7 @@ import re
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
from importlib import reload
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
def module_reload(filename):
|
def module_reload(filename):
|
||||||
|
|
|
@ -24,15 +24,8 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
import requests
|
import requests
|
||||||
try:
|
from unittest import mock
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from random import choice
|
from random import choice
|
||||||
|
@ -51,7 +44,7 @@ import logging
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
class AppriseURLTester(object):
|
class AppriseURLTester:
|
||||||
|
|
||||||
# Some exception handling we'll use
|
# Some exception handling we'll use
|
||||||
req_exceptions = (
|
req_exceptions = (
|
||||||
|
@ -151,7 +144,7 @@ class AppriseURLTester(object):
|
||||||
# Allow us to force the server response text to be something other then
|
# Allow us to force the server response text to be something other then
|
||||||
# the defaults
|
# the defaults
|
||||||
requests_response_text = meta.get('requests_response_text')
|
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
|
# Convert to string
|
||||||
requests_response_text = dumps(requests_response_text)
|
requests_response_text = dumps(requests_response_text)
|
||||||
|
|
||||||
|
@ -242,11 +235,11 @@ class AppriseURLTester(object):
|
||||||
|
|
||||||
# We loaded okay; now lets make sure we can reverse
|
# We loaded okay; now lets make sure we can reverse
|
||||||
# this url
|
# this url
|
||||||
assert isinstance(obj.url(), six.string_types) is True
|
assert isinstance(obj.url(), str) is True
|
||||||
|
|
||||||
# Test url() with privacy=True
|
# Test url() with privacy=True
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
obj.url(privacy=True), six.string_types) is True
|
obj.url(privacy=True), str) is True
|
||||||
|
|
||||||
# Some Simple Invalid Instance Testing
|
# Some Simple Invalid Instance Testing
|
||||||
assert instance.parse_url(None) is None
|
assert instance.parse_url(None) is None
|
||||||
|
@ -299,7 +292,7 @@ class AppriseURLTester(object):
|
||||||
print('%s AssertionError' % url)
|
print('%s AssertionError' % url)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Tidy our object and allow any possible defined deconstructors to
|
# Tidy our object and allow any possible defined destructors to
|
||||||
# be executed.
|
# be executed.
|
||||||
del obj
|
del obj
|
||||||
|
|
||||||
|
@ -346,7 +339,7 @@ class AppriseURLTester(object):
|
||||||
# Allow us to force the server response text to be something other then
|
# Allow us to force the server response text to be something other then
|
||||||
# the defaults
|
# the defaults
|
||||||
requests_response_text = meta.get('requests_response_text')
|
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
|
# Convert to string
|
||||||
requests_response_text = dumps(requests_response_text)
|
requests_response_text = dumps(requests_response_text)
|
||||||
|
|
||||||
|
|
|
@ -26,16 +26,9 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import six
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
try:
|
from unittest import mock
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
@ -58,19 +51,14 @@ from apprise.plugins import __reset_matrix
|
||||||
from apprise.utils import parse_list
|
from apprise.utils import parse_list
|
||||||
import inspect
|
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
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
import logging
|
||||||
logging.disable(logging.CRITICAL)
|
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
|
# Attachment Directory
|
||||||
TEST_VAR_DIR = join(dirname(__file__), 'var')
|
TEST_VAR_DIR = join(dirname(__file__), 'var')
|
||||||
|
|
||||||
|
@ -86,7 +74,6 @@ def test_apprise():
|
||||||
apprise_test(do_notify)
|
apprise_test(do_notify)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+")
|
|
||||||
def test_apprise_async():
|
def test_apprise_async():
|
||||||
"""
|
"""
|
||||||
API: Apprise() object asynchronous methods
|
API: Apprise() object asynchronous methods
|
||||||
|
@ -154,12 +141,12 @@ def apprise_test(do_notify):
|
||||||
assert len(a) == 2
|
assert len(a) == 2
|
||||||
|
|
||||||
# We can retrieve elements from our list too by reference:
|
# 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:
|
# We can iterate over our list too:
|
||||||
count = 0
|
count = 0
|
||||||
for o in a:
|
for o in a:
|
||||||
assert isinstance(o.url(), six.string_types) is True
|
assert isinstance(o.url(), str) is True
|
||||||
count += 1
|
count += 1
|
||||||
# verify that we did indeed iterate over each element
|
# verify that we did indeed iterate over each element
|
||||||
assert len(a) == count
|
assert len(a) == count
|
||||||
|
@ -547,7 +534,6 @@ def test_apprise_tagging(mock_post, mock_get):
|
||||||
|
|
||||||
@mock.patch('requests.get')
|
@mock.patch('requests.get')
|
||||||
@mock.patch('requests.post')
|
@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):
|
def test_apprise_tagging_async(mock_post, mock_get):
|
||||||
"""
|
"""
|
||||||
API: Apprise() object tagging functionality asynchronous methods
|
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
|
tag=[(object, ), ]) is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+")
|
|
||||||
def test_apprise_schemas(tmpdir):
|
def test_apprise_schemas(tmpdir):
|
||||||
"""
|
"""
|
||||||
API: Apprise().schema() tests
|
API: Apprise().schema() tests
|
||||||
|
@ -918,15 +903,6 @@ def test_apprise_asset(tmpdir):
|
||||||
must_exist=True) is not None
|
must_exist=True) is not None
|
||||||
|
|
||||||
# Test case where we can't access the image file
|
# Test case where we can't access the image file
|
||||||
if sys.version_info.major <= 2:
|
|
||||||
# Python v2.x
|
|
||||||
with mock.patch('__builtin__.open', side_effect=OSError()):
|
|
||||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
|
||||||
|
|
||||||
# Our content is retrivable again
|
|
||||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is not None
|
|
||||||
else:
|
|
||||||
# Python >= v3.x
|
|
||||||
with mock.patch('builtins.open', side_effect=OSError()):
|
with mock.patch('builtins.open', side_effect=OSError()):
|
||||||
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
assert a.image_raw(NotifyType.INFO, NotifyImageSize.XY_256) is None
|
||||||
|
|
||||||
|
@ -1376,7 +1352,7 @@ def test_apprise_details():
|
||||||
assert 'details' in entry['requirements']
|
assert 'details' in entry['requirements']
|
||||||
assert 'packages_required' in entry['requirements']
|
assert 'packages_required' in entry['requirements']
|
||||||
assert 'packages_recommended' 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_required'], list)
|
||||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||||
|
|
||||||
|
@ -1403,7 +1379,7 @@ def test_apprise_details():
|
||||||
assert 'details' in entry['requirements']
|
assert 'details' in entry['requirements']
|
||||||
assert 'packages_required' in entry['requirements']
|
assert 'packages_required' in entry['requirements']
|
||||||
assert 'packages_recommended' 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_required'], list)
|
||||||
assert isinstance(entry['requirements']['packages_recommended'], list)
|
assert isinstance(entry['requirements']['packages_recommended'], list)
|
||||||
|
|
||||||
|
@ -1498,7 +1474,7 @@ def test_apprise_details_plugin_verification():
|
||||||
# A Service Name MUST be defined
|
# A Service Name MUST be defined
|
||||||
assert 'service_name' in entry
|
assert 'service_name' in entry
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
entry['service_name'], (six.string_types, LazyTranslation))
|
entry['service_name'], (str, LazyTranslation))
|
||||||
|
|
||||||
# Acquire our protocols
|
# Acquire our protocols
|
||||||
protocols = parse_list(
|
protocols = parse_list(
|
||||||
|
@ -1527,10 +1503,10 @@ def test_apprise_details_plugin_verification():
|
||||||
if 'alias_of' not in arg:
|
if 'alias_of' not in arg:
|
||||||
# Minimum requirement of an argument
|
# Minimum requirement of an argument
|
||||||
assert 'name' in arg
|
assert 'name' in arg
|
||||||
assert isinstance(arg['name'], six.string_types)
|
assert isinstance(arg['name'], str)
|
||||||
|
|
||||||
assert 'type' in arg
|
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
|
assert is_valid_type_re.match(arg['type']) is not None
|
||||||
|
|
||||||
if 'min' in arg:
|
if 'min' in arg:
|
||||||
|
@ -1555,7 +1531,7 @@ def test_apprise_details_plugin_verification():
|
||||||
assert isinstance(arg['required'], bool)
|
assert isinstance(arg['required'], bool)
|
||||||
|
|
||||||
if 'prefix' in arg:
|
if 'prefix' in arg:
|
||||||
assert isinstance(arg['prefix'], six.string_types)
|
assert isinstance(arg['prefix'], str)
|
||||||
if section == 'kwargs':
|
if section == 'kwargs':
|
||||||
# The only acceptable prefix types for kwargs
|
# The only acceptable prefix types for kwargs
|
||||||
assert arg['prefix'] in (':', '+', '-')
|
assert arg['prefix'] in (':', '+', '-')
|
||||||
|
@ -1566,7 +1542,7 @@ def test_apprise_details_plugin_verification():
|
||||||
|
|
||||||
if 'map_to' in arg:
|
if 'map_to' in arg:
|
||||||
# must be a string
|
# must be a string
|
||||||
assert isinstance(arg['map_to'], six.string_types)
|
assert isinstance(arg['map_to'], str)
|
||||||
# Track our map_to object
|
# Track our map_to object
|
||||||
map_to_entries.add(arg['map_to'])
|
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)
|
# Regex must ALWAYS be in the format (regex, option)
|
||||||
assert isinstance(arg['regex'], (tuple, list))
|
assert isinstance(arg['regex'], (tuple, list))
|
||||||
assert len(arg['regex']) == 2
|
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(
|
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
|
# Compile the regular expression to verify that it is
|
||||||
# valid
|
# valid
|
||||||
|
@ -1632,10 +1608,10 @@ def test_apprise_details_plugin_verification():
|
||||||
|
|
||||||
# must be a string
|
# must be a string
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
arg['alias_of'], (six.string_types, list, tuple, set))
|
arg['alias_of'], (str, list, tuple, set))
|
||||||
|
|
||||||
aliases = [arg['alias_of']] \
|
aliases = [arg['alias_of']] \
|
||||||
if isinstance(arg['alias_of'], six.string_types) \
|
if isinstance(arg['alias_of'], str) \
|
||||||
else arg['alias_of']
|
else arg['alias_of']
|
||||||
|
|
||||||
for alias_of in aliases:
|
for alias_of in aliases:
|
||||||
|
@ -1687,7 +1663,7 @@ def test_apprise_details_plugin_verification():
|
||||||
# 'alias_of': ('apitoken', 'webtoken'),
|
# '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
|
assert len(entry['details'][section][key]) == 1
|
||||||
else: # is tuple,list, or set
|
else: # is tuple,list, or set
|
||||||
assert len(entry['details'][section][key]) == 2
|
assert len(entry['details'][section][key]) == 2
|
||||||
|
@ -1711,17 +1687,6 @@ def test_apprise_details_plugin_verification():
|
||||||
(tuple, set, list),
|
(tuple, set, list),
|
||||||
)
|
)
|
||||||
|
|
||||||
if six.PY2:
|
|
||||||
# inspect our object
|
|
||||||
# getargspec() is deprecated in Python v3
|
|
||||||
spec = inspect.getargspec(
|
|
||||||
common.NOTIFY_SCHEMA_MAP[protocols[0]].__init__)
|
|
||||||
|
|
||||||
function_args = \
|
|
||||||
(set(parse_list(spec.keywords)) - set(['kwargs'])) \
|
|
||||||
| (set(spec.args) - set(['self'])) | valid_kwargs
|
|
||||||
else:
|
|
||||||
# Python v3+ uses getfullargspec()
|
|
||||||
spec = inspect.getfullargspec(
|
spec = inspect.getfullargspec(
|
||||||
common.NOTIFY_SCHEMA_MAP[protocols[0]].__init__)
|
common.NOTIFY_SCHEMA_MAP[protocols[0]].__init__)
|
||||||
|
|
||||||
|
@ -1790,7 +1755,6 @@ def test_apprise_details_plugin_verification():
|
||||||
assert arg in defined_tokens
|
assert arg in defined_tokens
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.version_info.major <= 2, reason="Requires Python 3.x+")
|
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
@mock.patch('apprise.py3compat.asyncio.notify', wraps=py3aio.notify)
|
@mock.patch('apprise.py3compat.asyncio.notify', wraps=py3aio.notify)
|
||||||
def test_apprise_async_mode(mock_async_notify, mock_post, tmpdir):
|
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
|
# Test no app_id
|
||||||
base.join('NotifyBadFile1.py').write(
|
base.join('NotifyBadFile1.py').write(
|
||||||
"""
|
"""
|
||||||
class NotifyBadFile1(object):
|
class NotifyBadFile1:
|
||||||
pass""")
|
pass""")
|
||||||
|
|
||||||
# No class of the same name
|
# No class of the same name
|
||||||
base.join('NotifyBadFile2.py').write(
|
base.join('NotifyBadFile2.py').write(
|
||||||
"""
|
"""
|
||||||
class BadClassName(object):
|
class BadClassName:
|
||||||
pass""")
|
pass""")
|
||||||
|
|
||||||
# Exception thrown
|
# Exception thrown
|
||||||
|
|
|
@ -379,13 +379,13 @@ def test_attachment_matrix_dynamic_importing(tmpdir):
|
||||||
# Test no app_id
|
# Test no app_id
|
||||||
base.join('AttachBadFile1.py').write(
|
base.join('AttachBadFile1.py').write(
|
||||||
"""
|
"""
|
||||||
class AttachBadFile1(object):
|
class AttachBadFile1:
|
||||||
pass""")
|
pass""")
|
||||||
|
|
||||||
# No class of the same name
|
# No class of the same name
|
||||||
base.join('AttachBadFile2.py').write(
|
base.join('AttachBadFile2.py').write(
|
||||||
"""
|
"""
|
||||||
class BadClassName(object):
|
class BadClassName:
|
||||||
pass""")
|
pass""")
|
||||||
|
|
||||||
# Exception thrown
|
# Exception thrown
|
||||||
|
|
|
@ -24,17 +24,8 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import six
|
|
||||||
import io
|
|
||||||
try:
|
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from unittest import mock
|
||||||
from apprise import NotifyFormat
|
from apprise import NotifyFormat
|
||||||
from apprise import ConfigFormat
|
from apprise import ConfigFormat
|
||||||
from apprise import ContentIncludeMode
|
from apprise import ContentIncludeMode
|
||||||
|
@ -109,7 +100,7 @@ def test_apprise_config(tmpdir):
|
||||||
assert len(ac.servers()) == 4
|
assert len(ac.servers()) == 4
|
||||||
|
|
||||||
# Get our URL back
|
# 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
|
# Test cases where our URL is invalid
|
||||||
t = tmpdir.mkdir("strange-lines").join("apprise")
|
t = tmpdir.mkdir("strange-lines").join("apprise")
|
||||||
|
@ -156,13 +147,9 @@ def test_apprise_config(tmpdir):
|
||||||
# Iñtërnâtiônàlization Testing
|
# Iñtërnâtiônàlization Testing
|
||||||
windows://"""
|
windows://"""
|
||||||
|
|
||||||
if six.PY2:
|
|
||||||
# decode string into unicode
|
|
||||||
istr = istr.decode('utf-8')
|
|
||||||
|
|
||||||
# Write our content to our file
|
# Write our content to our file
|
||||||
t = tmpdir.mkdir("internationalization").join("apprise")
|
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'))
|
f.write(istr.encode('latin-1'))
|
||||||
|
|
||||||
# Create ourselves a config object
|
# Create ourselves a config object
|
||||||
|
@ -191,7 +178,7 @@ def test_apprise_config(tmpdir):
|
||||||
assert len(ac.servers()) == 1
|
assert len(ac.servers()) == 1
|
||||||
|
|
||||||
# Get our URL back
|
# Get our URL back
|
||||||
assert isinstance(ac[0].url(), six.string_types)
|
assert isinstance(ac[0].url(), str)
|
||||||
|
|
||||||
# pop an entry from our list
|
# pop an entry from our list
|
||||||
assert isinstance(ac.pop(0), ConfigBase) is True
|
assert isinstance(ac.pop(0), ConfigBase) is True
|
||||||
|
@ -329,7 +316,7 @@ def test_apprise_add_config():
|
||||||
assert len(ac.servers()) == 3
|
assert len(ac.servers()) == 3
|
||||||
|
|
||||||
# Get our URL back
|
# Get our URL back
|
||||||
assert isinstance(ac[0].url(), six.string_types)
|
assert isinstance(ac[0].url(), str)
|
||||||
|
|
||||||
# Test invalid content
|
# Test invalid content
|
||||||
assert ac.add_config(content=object()) is False
|
assert ac.add_config(content=object()) is False
|
||||||
|
@ -1012,13 +999,13 @@ def test_configmatrix_dynamic_importing(tmpdir):
|
||||||
# Test no app_id
|
# Test no app_id
|
||||||
base.join('ConfigBadFile1.py').write(
|
base.join('ConfigBadFile1.py').write(
|
||||||
"""
|
"""
|
||||||
class ConfigBadFile1(object):
|
class ConfigBadFile1:
|
||||||
pass""")
|
pass""")
|
||||||
|
|
||||||
# No class of the same name
|
# No class of the same name
|
||||||
base.join('ConfigBadFile2.py').write(
|
base.join('ConfigBadFile2.py').write(
|
||||||
"""
|
"""
|
||||||
class BadClassName(object):
|
class BadClassName:
|
||||||
pass""")
|
pass""")
|
||||||
|
|
||||||
# Exception thrown
|
# Exception thrown
|
||||||
|
|
|
@ -27,15 +27,8 @@ from __future__ import print_function
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import six
|
|
||||||
from inspect import cleandoc
|
from inspect import cleandoc
|
||||||
try:
|
from urllib.parse import unquote
|
||||||
# Python 2.7
|
|
||||||
from urllib import unquote
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 3.x
|
|
||||||
from urllib.parse import unquote
|
|
||||||
|
|
||||||
from apprise import utils
|
from apprise import utils
|
||||||
from apprise import common
|
from apprise import common
|
||||||
|
@ -46,8 +39,6 @@ logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
# Ensure we don't create .pyc files for these tests
|
# Ensure we don't create .pyc files for these tests
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
# Python v2.x support requires an environment variable
|
|
||||||
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_qsd():
|
def test_parse_qsd():
|
||||||
|
@ -1937,7 +1928,7 @@ def test_parse_list():
|
||||||
'.xvid', '.wmv', '.mp4',
|
'.xvid', '.wmv', '.mp4',
|
||||||
])
|
])
|
||||||
|
|
||||||
class StrangeObject(object):
|
class StrangeObject:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '.avi'
|
return '.avi'
|
||||||
|
|
||||||
|
@ -2541,39 +2532,39 @@ def test_apply_templating():
|
||||||
|
|
||||||
result = utils.apply_template(
|
result = utils.apply_template(
|
||||||
template, **{'fname': 'Chris', 'whence': 'this morning'})
|
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?"
|
assert result == "Hello Chris, How are you this morning?"
|
||||||
|
|
||||||
# In this example 'whence' isn't provided, so it isn't swapped
|
# In this example 'whence' isn't provided, so it isn't swapped
|
||||||
result = utils.apply_template(
|
result = utils.apply_template(
|
||||||
template, **{'fname': 'Chris'})
|
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}}?"
|
assert result == "Hello Chris, How are you {{whence}}?"
|
||||||
|
|
||||||
# white space won't cause any ill affects:
|
# white space won't cause any ill affects:
|
||||||
template = "Hello {{ fname }}, How are you {{ whence}}?"
|
template = "Hello {{ fname }}, How are you {{ whence}}?"
|
||||||
result = utils.apply_template(
|
result = utils.apply_template(
|
||||||
template, **{'fname': 'Chris', 'whence': 'this morning'})
|
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?"
|
assert result == "Hello Chris, How are you this morning?"
|
||||||
|
|
||||||
# No arguments won't cause any problems
|
# No arguments won't cause any problems
|
||||||
template = "Hello {{fname}}, How are you {{whence}}?"
|
template = "Hello {{fname}}, How are you {{whence}}?"
|
||||||
result = utils.apply_template(template)
|
result = utils.apply_template(template)
|
||||||
assert isinstance(result, six.string_types) is True
|
assert isinstance(result, str) is True
|
||||||
assert result == template
|
assert result == template
|
||||||
|
|
||||||
# Wrong elements are simply ignored
|
# Wrong elements are simply ignored
|
||||||
result = utils.apply_template(
|
result = utils.apply_template(
|
||||||
template,
|
template,
|
||||||
**{'fname': 'l2g', 'whence': 'this evening', 'ignore': 'me'})
|
**{'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?"
|
assert result == "Hello l2g, How are you this evening?"
|
||||||
|
|
||||||
# Empty template makes things easy
|
# Empty template makes things easy
|
||||||
result = utils.apply_template(
|
result = utils.apply_template(
|
||||||
"", **{'fname': 'l2g', 'whence': 'this evening'})
|
"", **{'fname': 'l2g', 'whence': 'this evening'})
|
||||||
assert isinstance(result, six.string_types) is True
|
assert isinstance(result, str) is True
|
||||||
assert result == ""
|
assert result == ""
|
||||||
|
|
||||||
# Regular expressions are safely escapped and act as normal
|
# Regular expressions are safely escapped and act as normal
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import six
|
|
||||||
import sys
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
from apprise import Apprise
|
from apprise import Apprise
|
||||||
|
@ -33,15 +32,14 @@ from apprise import NotifyFormat
|
||||||
|
|
||||||
from apprise.common import NOTIFY_SCHEMA_MAP
|
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
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
import logging
|
||||||
logging.disable(logging.CRITICAL)
|
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")
|
reason="Requires Python 3.0 to 3.6")
|
||||||
def test_apprise_asyncio_runtime_error():
|
def test_apprise_asyncio_runtime_error():
|
||||||
"""
|
"""
|
||||||
|
@ -112,7 +110,7 @@ def test_apprise_asyncio_runtime_error():
|
||||||
asyncio.set_event_loop(loop)
|
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+")
|
reason="Requires Python 3.7+")
|
||||||
def test_apprise_works_in_async_loop():
|
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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
try:
|
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from unittest import mock
|
||||||
from apprise.attachment.AttachBase import AttachBase
|
from apprise.attachment.AttachBase import AttachBase
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
# Disable logging for a cleaner testing output
|
||||||
|
|
|
@ -25,13 +25,7 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
try:
|
from unittest import mock
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
|
|
@ -24,14 +24,7 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import six
|
from unittest import mock
|
||||||
try:
|
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
@ -140,7 +133,7 @@ def test_attach_http(mock_get):
|
||||||
# Temporary path
|
# Temporary path
|
||||||
path = join(TEST_VAR_DIR, 'apprise-test.gif')
|
path = join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||||
|
|
||||||
class DummyResponse(object):
|
class DummyResponse:
|
||||||
"""
|
"""
|
||||||
A dummy response used to manage our object
|
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')
|
'http://user:pass@localhost/apprise.gif?dl=1&cache=300')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# Test that our extended variables are passed along
|
||||||
assert mock_get.call_count == 0
|
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')
|
'http://user:pass@localhost/apprise.gif?+key=value&cache=True')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
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')
|
'http://localhost:3000/noname.gif?name=usethis.jpg&mime=image/jpeg')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# both mime and name over-ridden
|
||||||
assert re.search(r'[?&]mime=image%2Fjpeg', attachment.url())
|
assert re.search(r'[?&]mime=image%2Fjpeg', attachment.url())
|
||||||
assert re.search(r'[?&]name=usethis.jpg', 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')
|
results = AttachHTTP.parse_url('http://localhost')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
assert re.search(r'[?&]mime=', attachment.url()) is None
|
||||||
|
@ -279,7 +272,7 @@ def test_attach_http(mock_get):
|
||||||
attachment = AttachHTTP(**results)
|
attachment = AttachHTTP(**results)
|
||||||
# we can not download this attachment
|
# we can not download this attachment
|
||||||
assert not 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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
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')
|
results = AttachHTTP.parse_url('http://localhost/no-length.gif')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
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')
|
results = AttachHTTP.parse_url('http://user@localhost/ignore-filename.gif')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
assert re.search(r'[?&]mime=', attachment.url()) is None
|
||||||
|
@ -347,7 +340,7 @@ def test_attach_http(mock_get):
|
||||||
attachment = AttachHTTP(**results)
|
attachment = AttachHTTP(**results)
|
||||||
# we can not download this attachment
|
# we can not download this attachment
|
||||||
assert not 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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
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')
|
results = AttachHTTP.parse_url('http://user@localhost')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
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')
|
results = AttachHTTP.parse_url('http://localhost/invalid-length.gif')
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
attachment = AttachHTTP(**results)
|
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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
assert re.search(r'[?&]mime=', attachment.url()) is None
|
||||||
|
@ -399,7 +392,7 @@ def test_attach_http(mock_get):
|
||||||
attachment = AttachHTTP(**results)
|
attachment = AttachHTTP(**results)
|
||||||
# we can not download this attachment
|
# we can not download this attachment
|
||||||
assert 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
|
# No mime-type and/or filename over-ride was specified, so therefore it
|
||||||
# won't show up in the generated URL
|
# won't show up in the generated URL
|
||||||
assert re.search(r'[?&]mime=', attachment.url()) is None
|
assert re.search(r'[?&]mime=', attachment.url()) is None
|
||||||
|
|
|
@ -24,13 +24,7 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import re
|
import re
|
||||||
try:
|
from unittest import mock
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
@ -47,17 +41,8 @@ from apprise.utils import environ
|
||||||
from apprise.plugins import __load_matrix
|
from apprise.plugins import __load_matrix
|
||||||
from apprise.plugins import __reset_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
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
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...
|
# We can find our new hook loaded in our NOTIFY_SCHEMA_MAP now...
|
||||||
assert 'clihook' in NOTIFY_SCHEMA_MAP
|
assert 'clihook' in NOTIFY_SCHEMA_MAP
|
||||||
|
|
||||||
# Store our key after parsing it as a list (this makes this test backwards
|
# Capture our key for reference
|
||||||
# compatible with Python 2.x
|
|
||||||
key = [k for k in NOTIFY_CUSTOM_MODULE_MAP.keys()][0]
|
key = [k for k in NOTIFY_CUSTOM_MODULE_MAP.keys()][0]
|
||||||
|
|
||||||
assert len(NOTIFY_CUSTOM_MODULE_MAP[key]['notify']) == 1
|
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 'clihook1' in NOTIFY_SCHEMA_MAP
|
||||||
assert 'clihook2' in NOTIFY_SCHEMA_MAP
|
assert 'clihook2' in NOTIFY_SCHEMA_MAP
|
||||||
|
|
||||||
# Store our key after parsing it as a list (this makes this test backwards
|
# Capture our key for reference
|
||||||
# compatible with Python 2.x
|
|
||||||
key = [k for k in NOTIFY_CUSTOM_MODULE_MAP.keys()][0]
|
key = [k for k in NOTIFY_CUSTOM_MODULE_MAP.keys()][0]
|
||||||
|
|
||||||
assert len(NOTIFY_CUSTOM_MODULE_MAP[key]['notify']) == 3
|
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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
|
||||||
import pytest
|
import pytest
|
||||||
from apprise.AppriseAsset import AppriseAsset
|
from apprise.AppriseAsset import AppriseAsset
|
||||||
from apprise.config.ConfigBase import ConfigBase
|
from apprise.config.ConfigBase import ConfigBase
|
||||||
|
@ -754,9 +753,9 @@ urls:
|
||||||
assert asset.theme == AppriseAsset().theme
|
assert asset.theme == AppriseAsset().theme
|
||||||
|
|
||||||
# Empty string assignment
|
# 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 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 == ""
|
assert asset.image_url_logo == ""
|
||||||
|
|
||||||
# For on-lookers looking through this file; here is a perfectly formatted
|
# 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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
import six
|
from unittest import mock
|
||||||
try:
|
|
||||||
# Python 3.x
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Python 2.7
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from apprise.config.ConfigFile import ConfigFile
|
from apprise.config.ConfigFile import ConfigFile
|
||||||
from apprise.plugins.NotifyBase import NotifyBase
|
from apprise.plugins.NotifyBase import NotifyBase
|
||||||
|
@ -64,7 +57,7 @@ def test_config_file(tmpdir):
|
||||||
# one entry added
|
# one entry added
|
||||||
assert len(cf) == 1
|
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
|
# Verify that we're using the same asset
|
||||||
assert cf[0].asset is asset
|
assert cf[0].asset is asset
|
||||||
|
@ -102,8 +95,8 @@ def test_config_file(tmpdir):
|
||||||
'file://{}?cache=30'.format(str(t)))
|
'file://{}?cache=30'.format(str(t)))
|
||||||
assert isinstance(results, dict)
|
assert isinstance(results, dict)
|
||||||
cf = ConfigFile(**results)
|
cf = ConfigFile(**results)
|
||||||
assert isinstance(cf.url(), six.string_types) is True
|
assert isinstance(cf.url(), str) is True
|
||||||
assert isinstance(cf.read(), six.string_types) is True
|
assert isinstance(cf.read(), str) is True
|
||||||
|
|
||||||
|
|
||||||
def test_config_file_exceptions(tmpdir):
|
def test_config_file_exceptions(tmpdir):
|
||||||
|
@ -120,7 +113,7 @@ def test_config_file_exceptions(tmpdir):
|
||||||
cf = ConfigFile(path=str(t), format='text')
|
cf = ConfigFile(path=str(t), format='text')
|
||||||
|
|
||||||
# Internal Exception would have been thrown and this would fail
|
# 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
|
assert cf.read() is None
|
||||||
|
|
||||||
# handle case where the file is to large for what was expected:
|
# 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