Return target count from len(Service) calls (#874)

This commit is contained in:
Chris Caron 2023-05-12 22:05:46 -04:00 committed by GitHub
parent e83cba66a6
commit c542ab23bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 748 additions and 61 deletions

View File

@ -685,6 +685,15 @@ class URLBase:
return response
def __len__(self):
"""
Should be over-ridden and allows the tracking of how many targets
are associated with each URLBase object.
Default is always 1
"""
return 1
def schemas(self):
"""A simple function that returns a set of all schemas associated
with this object based on the object.protocol and

View File

@ -280,7 +280,7 @@ class NotifyBark(NotifyBase):
# error tracking (used for function return)
has_error = False
if not len(self.targets):
if not self.targets:
# We have nothing to notify; we're done
self.logger.warning('There are no Bark devices to notify')
return False
@ -456,6 +456,12 @@ class NotifyBark(NotifyBase):
params=NotifyBark.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -46,6 +46,7 @@ except ImportError:
from .NotifyBase import NotifyBase
from ..URLBase import PrivacyMode
from ..utils import parse_bool
from ..utils import parse_list
from ..utils import validate_regex
from ..common import NotifyType
from ..common import NotifyImageSize
@ -58,7 +59,7 @@ DEFAULT_TAG = '@all'
# list of tagged devices that the notification need to be send to, and a
# boolean operator (and / or) that defines the criteria to match devices
# against those tags.
IS_TAG = re.compile(r'^[@](?P<name>[A-Z0-9]{1,63})$', re.I)
IS_TAG = re.compile(r'^[@]?(?P<name>[A-Z0-9]{1,63})$', re.I)
# Device tokens are only referenced when developing.
# It's not likely you'll send a message directly to a device, but if you do;
@ -160,7 +161,7 @@ class NotifyBoxcar(NotifyBase):
super().__init__(**kwargs)
# Initialize tag list
self.tags = list()
self._tags = list()
# Initialize device_token list
self.device_tokens = list()
@ -184,29 +185,27 @@ class NotifyBoxcar(NotifyBase):
raise TypeError(msg)
if not targets:
self.tags.append(DEFAULT_TAG)
self._tags.append(DEFAULT_TAG)
targets = []
elif isinstance(targets, str):
targets = [x for x in filter(bool, TAGS_LIST_DELIM.split(
targets,
))]
# Validate targets and drop bad ones:
for target in targets:
if IS_TAG.match(target):
for target in parse_list(targets):
result = IS_TAG.match(target)
if result:
# store valid tag/alias
self.tags.append(IS_TAG.match(target).group('name'))
self._tags.append(result.group('name'))
continue
elif IS_DEVICETOKEN.match(target):
result = IS_DEVICETOKEN.match(target)
if result:
# store valid device
self.device_tokens.append(target)
continue
else:
self.logger.warning(
'Dropped invalid tag/alias/device_token '
'({}) specified.'.format(target),
)
self.logger.warning(
'Dropped invalid tag/alias/device_token '
'({}) specified.'.format(target),
)
# Track whether or not we want to send an image with our notification
# or not.
@ -238,8 +237,8 @@ class NotifyBoxcar(NotifyBase):
if body:
payload['aps']['alert'] = body
if self.tags:
payload['tags'] = {'or': self.tags}
if self._tags:
payload['tags'] = {'or': self._tags}
if self.device_tokens:
payload['device_tokens'] = self.device_tokens
@ -341,10 +340,18 @@ class NotifyBoxcar(NotifyBase):
self.secret, privacy, mode=PrivacyMode.Secret, safe=''),
targets='/'.join([
NotifyBoxcar.quote(x, safe='') for x in chain(
self.tags, self.device_tokens) if x != DEFAULT_TAG]),
self._tags, self.device_tokens) if x != DEFAULT_TAG]),
params=NotifyBoxcar.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self._tags) + len(self.device_tokens)
# DEFAULT_TAG is set if no tokens/tags are otherwise set
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -414,6 +414,24 @@ class NotifyBulkSMS(NotifyBase):
for x in self.groups])),
params=NotifyBulkSMS.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
# Note: Groups always require a separate request (and can not be
# included in batch calculations)
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets + len(self.groups)
@staticmethod
def parse_url(url):
"""

View File

@ -288,6 +288,21 @@ class NotifyClickSend(NotifyBase):
params=NotifyClickSend.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets
@staticmethod
def parse_url(url):
"""

View File

@ -357,6 +357,15 @@ class NotifyD7Networks(NotifyBase):
[NotifyD7Networks.quote(x, safe='') for x in self.targets]),
params=NotifyD7Networks.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
return len(self.targets) if not self.batch else 1
@staticmethod
def parse_url(url):
"""

View File

@ -350,6 +350,21 @@ class NotifyDapnet(NotifyBase):
params=NotifyDapnet.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets
@staticmethod
def parse_url(url):
"""

View File

@ -309,6 +309,13 @@ class NotifyDingTalk(NotifyBase):
[NotifyDingTalk.quote(x, safe='') for x in self.targets]),
args=NotifyDingTalk.urlencode(args))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -999,6 +999,13 @@ class NotifyEmail(NotifyBase):
params=NotifyEmail.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -555,6 +555,12 @@ class NotifyFCM(NotifyBase):
params=NotifyFCM.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -334,6 +334,13 @@ class NotifyFlock(NotifyBase):
params=NotifyFlock.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -389,6 +389,12 @@ class NotifyGitter(NotifyBase):
[NotifyGitter.quote(x, safe='') for x in self.targets]),
params=NotifyGitter.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -312,6 +312,12 @@ class NotifyIFTTT(NotifyBase):
params=NotifyIFTTT.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.events)
@staticmethod
def parse_url(url):
"""

View File

@ -373,6 +373,12 @@ class NotifyJoin(NotifyBase):
for x in self.targets]),
params=NotifyJoin.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -324,6 +324,12 @@ class NotifyKavenegar(NotifyBase):
[NotifyKavenegar.quote(x, safe='') for x in self.targets]),
params=NotifyKavenegar.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -267,6 +267,12 @@ class NotifyLine(NotifyBase):
params=NotifyLine.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -476,6 +476,12 @@ class NotifyMQTT(NotifyBase):
params=NotifyMQTT.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.topics)
@staticmethod
def parse_url(url):
"""

View File

@ -329,6 +329,12 @@ class NotifyMSG91(NotifyBase):
[NotifyMSG91.quote(x, safe='') for x in self.targets]),
params=NotifyMSG91.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -627,6 +627,20 @@ class NotifyMailgun(NotifyBase):
safe='') for e in self.targets]),
params=NotifyMailgun.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -378,6 +378,13 @@ class NotifyMastodon(NotifyBase):
params=NotifyMastodon.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
**kwargs):
"""

View File

@ -1196,6 +1196,13 @@ class NotifyMatrix(NotifyBase):
params=NotifyMatrix.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.rooms)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -311,6 +311,13 @@ class NotifyMessageBird(NotifyBase):
[NotifyMessageBird.quote(x, safe='') for x in self.targets]),
params=NotifyMessageBird.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -312,6 +312,12 @@ class NotifyNextcloud(NotifyBase):
params=NotifyNextcloud.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -263,6 +263,12 @@ class NotifyNextcloudTalk(NotifyBase):
for x in self.targets]),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -698,6 +698,12 @@ class NotifyNtfy(NotifyBase):
params=NotifyNtfy.urlencode(params)
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.topics)
@staticmethod
def parse_url(url):
"""

View File

@ -596,6 +596,12 @@ class NotifyOffice365(NotifyBase):
safe='') for e in self.targets]),
params=NotifyOffice365.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -51,7 +51,7 @@ from ..utils import is_email
from ..AppriseLocale import gettext_lazy as _
class OneSignalCategory(NotifyBase):
class OneSignalCategory:
"""
We define the different category types that we can notify via OneSignal
"""
@ -92,7 +92,7 @@ class NotifyOneSignal(NotifyBase):
image_size = NotifyImageSize.XY_72
# The maximum allowable batch sizes per message
maximum_batch_size = 2000
default_batch_size = 2000
# Define object templates
templates = (
@ -121,7 +121,7 @@ class NotifyOneSignal(NotifyBase):
'private': True,
'required': True,
},
'target_device': {
'target_player': {
'name': _('Target Player ID'),
'type': 'string',
'map_to': 'targets',
@ -204,7 +204,7 @@ class NotifyOneSignal(NotifyBase):
raise TypeError(msg)
# Prepare Batch Mode Flag
self.batch_size = self.maximum_batch_size if batch else 1
self.batch_size = self.default_batch_size if batch else 1
# Place a thumbnail image inline with the message body
self.include_image = include_image
@ -432,6 +432,26 @@ class NotifyOneSignal(NotifyBase):
params=NotifyOneSignal.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
if self.batch_size > 1:
# Batches can only be sent by group (you can't combine groups into
# a single batch)
total_targets = 0
for k, m in self.targets.items():
targets = len(m)
total_targets += int(targets / self.batch_size) + \
(1 if targets % self.batch_size else 0)
return total_targets
# Normal batch count; just count the targets
return sum([len(m) for _, m in self.targets.items()])
@staticmethod
def parse_url(url):
"""

View File

@ -172,7 +172,7 @@ class NotifyOpsgenie(NotifyBase):
opsgenie_default_region = OpsgenieRegion.US
# The maximum allowable targets within a notification
maximum_batch_size = 50
default_batch_size = 50
# Define object templates
templates = (
@ -308,7 +308,7 @@ class NotifyOpsgenie(NotifyBase):
self.details.update(details)
# Prepare Batch Mode Flag
self.batch_size = self.maximum_batch_size if batch else 1
self.batch_size = self.default_batch_size if batch else 1
# Assign our tags (if defined)
self.__tags = parse_list(tags)
@ -536,6 +536,20 @@ class NotifyOpsgenie(NotifyBase):
for x in self.targets]),
params=NotifyOpsgenie.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
targets = len(self.targets)
if self.batch_size > 1:
targets = int(targets / self.batch_size) + \
(1 if targets % self.batch_size else 0)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -265,6 +265,21 @@ class NotifyPopcornNotify(NotifyBase):
[NotifyPopcornNotify.quote(x, safe='') for x in self.targets]),
params=NotifyPopcornNotify.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets
@staticmethod
def parse_url(url):
"""

View File

@ -406,6 +406,12 @@ class NotifyPushBullet(NotifyBase):
targets=targets,
params=NotifyPushBullet.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -794,6 +794,12 @@ class NotifyPushSafer(NotifyBase):
targets=targets,
params=NotifyPushSafer.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -329,6 +329,13 @@ class NotifyPushed(NotifyBase):
)]),
params=NotifyPushed.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.channels) + len(self.users)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -577,6 +577,12 @@ class NotifyPushover(NotifyBase):
devices=devices,
params=NotifyPushover.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -367,6 +367,12 @@ class NotifyReddit(NotifyBase):
params=NotifyReddit.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.subreddits)
def login(self):
"""
A simple wrapper to authenticate with the Reddit Server

View File

@ -348,6 +348,13 @@ class NotifyRocketChat(NotifyBase):
params=NotifyRocketChat.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.channels) + len(self.rooms) + len(self.users)
return targets if targets > 0 else 1
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
wrapper to _send since we can alert more then one channel

View File

@ -816,6 +816,13 @@ class NotifySES(NotifyBase):
params=NotifySES.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -73,6 +73,22 @@ SMSEAGLE_PRIORITY_MAP = {
}
class SMSEagleCategory:
"""
We define the different category types that we can notify via SMS Eagle
"""
PHONE = 'phone'
GROUP = 'group'
CONTACT = 'contact'
SMSEAGLE_CATEGORIES = (
SMSEagleCategory.PHONE,
SMSEagleCategory.GROUP,
SMSEagleCategory.CONTACT,
)
class NotifySMSEagle(NotifyBase):
"""
A wrapper for SMSEagle Notifications
@ -403,15 +419,15 @@ class NotifySMSEagle(NotifyBase):
batch_size = 1 if not self.batch else self.default_batch_size
notify_by = {
'phone': {
SMSEagleCategory.PHONE: {
"method": "sms.send_sms",
'target': 'to',
},
'group': {
SMSEagleCategory.GROUP: {
"method": "sms.send_togroup",
'target': 'groupname',
},
'contact': {
SMSEagleCategory.CONTACT: {
"method": "sms.send_tocontact",
'target': 'contactname',
},
@ -420,7 +436,7 @@ class NotifySMSEagle(NotifyBase):
# categories separated into a tuple since notify_by.keys()
# returns an unpredicable list in Python 2.7 which causes
# tests to fail every so often
for category in ('phone', 'group', 'contact'):
for category in SMSEAGLE_CATEGORIES:
# Create a copy of our template
payload = {
'method': notify_by[category]['method'],
@ -596,6 +612,28 @@ class NotifySMSEagle(NotifyBase):
params=NotifySMSEagle.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
if batch_size > 1:
# Batches can only be sent by group (you can't combine groups into
# a single batch)
total_targets = 0
for c in SMSEAGLE_CATEGORIES:
targets = len(getattr(self, f'target_{c}s'))
total_targets += int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return total_targets
# Normal batch count; just count the targets
return len(self.target_phones) + len(self.target_contacts) + \
len(self.target_groups)
@staticmethod
def parse_url(url):
"""

View File

@ -513,6 +513,21 @@ class NotifySMTP2Go(NotifyBase):
safe='') for e in self.targets]),
params=NotifySMTP2Go.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -600,6 +600,12 @@ class NotifySNS(NotifyBase):
params=NotifySNS.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.phone) + len(self.topics)
@staticmethod
def parse_url(url):
"""

View File

@ -287,6 +287,12 @@ class NotifySendGrid(NotifyBase):
params=NotifySendGrid.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform SendGrid Notification

View File

@ -431,6 +431,21 @@ class NotifySignalAPI(NotifyBase):
params=NotifySignalAPI.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets
@staticmethod
def parse_url(url):
"""

View File

@ -408,6 +408,13 @@ class NotifySinch(NotifyBase):
[NotifySinch.quote(x, safe='') for x in self.targets]),
params=NotifySinch.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -1015,6 +1015,12 @@ class NotifySlack(NotifyBase):
params=NotifySlack.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.channels)
@staticmethod
def parse_url(url):
"""

View File

@ -722,6 +722,21 @@ class NotifySparkPost(NotifyBase):
safe='') for e in self.targets]),
params=NotifySparkPost.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
#
# Factor batch into calculation
#
batch_size = 1 if not self.batch else self.default_batch_size
targets = len(self.targets)
if batch_size > 1:
targets = int(targets / batch_size) + \
(1 if targets % batch_size else 0)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -350,6 +350,13 @@ class NotifySpontit(NotifyBase):
[NotifySpontit.quote(x, safe='') for x in self.targets]),
params=NotifySpontit.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -819,6 +819,12 @@ class NotifyTelegram(NotifyBase):
[NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]),
params=NotifyTelegram.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -385,6 +385,13 @@ class NotifyTwilio(NotifyBase):
[NotifyTwilio.quote(x, safe='') for x in self.targets]),
params=NotifyTwilio.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -256,6 +256,12 @@ class NotifyTwist(NotifyBase):
params=NotifyTwist.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.channels) + len(self.channel_ids)
def login(self):
"""
A simple wrapper to authenticate with the Twist Server

View File

@ -793,10 +793,6 @@ class NotifyTwitter(NotifyBase):
# Extend our parameters
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
if len(self.targets) > 0:
params['to'] = ','.join(
[NotifyTwitter.quote(x, safe='') for x in self.targets])
return '{schema}://{ckey}/{csecret}/{akey}/{asecret}' \
'/{targets}/?{params}'.format(
schema=self.secure_protocol[0],
@ -811,6 +807,13 @@ class NotifyTwitter(NotifyBase):
for target in self.targets]),
params=NotifyTwitter.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""
@ -823,28 +826,22 @@ class NotifyTwitter(NotifyBase):
# We're done early as we couldn't load the results
return results
# The first token is stored in the hostname
consumer_key = NotifyTwitter.unquote(results['host'])
# Acquire remaining tokens
tokens = NotifyTwitter.split_path(results['fullpath'])
# The consumer token is stored in the hostname
results['ckey'] = NotifyTwitter.unquote(results['host'])
#
# Now fetch the remaining tokens
try:
consumer_secret, access_token_key, access_token_secret = \
tokens[0:3]
#
except (ValueError, AttributeError, IndexError):
# Force some bad values that will get caught
# in parsing later
consumer_secret = None
access_token_key = None
access_token_secret = None
results['ckey'] = consumer_key
results['csecret'] = consumer_secret
results['akey'] = access_token_key
results['asecret'] = access_token_secret
# Consumer Secret
results['csecret'] = tokens.pop(0) if tokens else None
# Access Token Key
results['akey'] = tokens.pop(0) if tokens else None
# Access Token Secret
results['asecret'] = tokens.pop(0) if tokens else None
# The defined twitter mode
if 'mode' in results['qsd'] and len(results['qsd']['mode']):
@ -861,7 +858,7 @@ class NotifyTwitter(NotifyBase):
results['targets'].append(results.get('user'))
# Store any remaining items as potential targets
results['targets'].extend(tokens[3:])
results['targets'].extend(tokens)
# Get Cache Flag (reduces lookup hits)
if 'cache' in results['qsd'] and len(results['qsd']['cache']):

View File

@ -329,6 +329,13 @@ class NotifyVoipms(NotifyBase):
['1' + NotifyVoipms.quote(x, safe='') for x in self.targets]),
params=NotifyVoipms.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -334,6 +334,13 @@ class NotifyVonage(NotifyBase):
[NotifyVonage.quote(x, safe='') for x in self.targets]),
params=NotifyVonage.urlencode(params))
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
targets = len(self.targets)
return targets if targets > 0 else 1
@staticmethod
def parse_url(url):
"""

View File

@ -359,6 +359,12 @@ class NotifyZulip(NotifyBase):
params=NotifyZulip.urlencode(params),
)
def __len__(self):
"""
Returns the number of targets associated with this notification
"""
return len(self.targets)
@staticmethod
def parse_url(url):
"""

View File

@ -203,6 +203,9 @@ class AppriseURLTester:
# this url
assert isinstance(obj.url(), str) is True
# Verify we can acquire a target count as an integer
assert isinstance(len(obj), int)
# Test url() with privacy=True
assert isinstance(
obj.url(privacy=True), str) is True
@ -240,6 +243,14 @@ class AppriseURLTester:
url, obj.url()))
assert False
# Verify there is no change from the old and the new
if len(obj) != len(obj_cmp):
print('%d targets found in %s' % (
len(obj), obj.url(privacy=True)))
print('But %d targets found in %s' % (
len(obj_cmp), obj_cmp.url(privacy=True)))
raise AssertionError("Target miscount %d != %d")
# Tidy our object
del obj_cmp
@ -371,11 +382,19 @@ class AppriseURLTester:
try:
if test_requests_exceptions is False:
# Verify we can acquire a target count as an integer
targets = len(obj)
# check that we're as expected
assert obj.notify(
body=self.body, title=self.title,
notify_type=notify_type) == notify_response
if notify_response:
# If we successfully got a response, there must have been
# at least 1 target present
assert targets > 0
# check that this doesn't change using different overflow
# methods
assert obj.notify(
@ -447,6 +466,7 @@ class AppriseURLTester:
os.path.join(self.__test_var_dir, 'apprise-test.png'),
os.path.join(self.__test_var_dir, 'apprise-test.jpeg'),
))
assert obj.notify(
body=self.body, title=self.title,
notify_type=notify_type,

View File

@ -168,8 +168,14 @@ def test_plugin_boxcar_edge_cases(mock_post, mock_get):
# Test comma, separate values
device = 'a' * 64
p = NotifyBoxcar(
access=access, secret=secret,
targets=','.join([device, device, device]))
# unique entries are colapsed into 1
assert len(p.device_tokens) == 1
p = NotifyBoxcar(
access=access, secret=secret,
targets=','.join(['a' * 64, 'b' * 64, 'c' * 64]))
# not unique, so we get the same data here
assert len(p.device_tokens) == 3

View File

@ -173,6 +173,9 @@ def test_plugin_bulksms_edge_cases(mock_post):
assert obj.notify(
body='body', title='title', notify_type=NotifyType.INFO) is True
# We know there are 4 targets
assert len(obj) == 4
# Test our call count
assert mock_post.call_count == 4
@ -205,3 +208,9 @@ def test_plugin_bulksms_edge_cases(mock_post):
['+15551231234', '+15555555555', '@group', '@12'])))
assert 'batch=no' in obj.url()
# With our batch in place, our calculations are different
obj = Apprise.instantiate(
'bulksms://{}:{}@{}?batch=y'.format(user, pwd, '/'.join(targets)))
# 2 groups and 2 phones are lumped together
assert len(obj) == 3

View File

@ -36,6 +36,7 @@ from unittest import mock
import apprise
from apprise.plugins.NotifyDapnet import DapnetPriority, NotifyDapnet
from helpers import AppriseURLTester
from apprise import NotifyType
# Disable logging for a cleaner testing output
import logging
@ -141,6 +142,35 @@ def test_plugin_dapnet_urls():
AppriseURLTester(tests=apprise_url_tests).run_all()
@mock.patch('requests.post')
def test_plugin_dapnet_edge_cases(mock_post):
"""
NotifyDapnet() Edge Cases
"""
# Prepare Mock
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.created
# test the handling of our batch modes
obj = apprise.Apprise.instantiate(
'dapnet://user:pass@{}?batch=yes'.format(
'/'.join(['DF1ABC', 'DF1DEF'])))
assert isinstance(obj, NotifyDapnet)
# objects will be combined into a single post in batch mode
assert len(obj) == 1
# Force our batch to break into separate messages
obj.default_batch_size = 1
# We'll send 2 messages now
assert len(obj) == 2
assert obj.notify(
body='body', title='title', notify_type=NotifyType.INFO) is True
assert mock_post.call_count == 2
@mock.patch('requests.post')
def test_plugin_dapnet_config_files(mock_post):
"""

View File

@ -342,6 +342,9 @@ def test_plugin_email(mock_smtp, mock_smtpssl):
# We loaded okay; now lets make sure we can reverse this url
assert isinstance(obj.url(), str) is True
# Verify we can acquire a target count as an integer
assert isinstance(len(obj), int)
# Test url() with privacy=True
assert isinstance(
obj.url(privacy=True), str) is True
@ -369,6 +372,14 @@ def test_plugin_email(mock_smtp, mock_smtpssl):
url, obj.url()))
assert False
# Verify there is no change from the old and the new
if len(obj) != len(obj_cmp):
print('%d targets found in %s' % (
len(obj), obj.url(privacy=True)))
print('But %d targets found in %s' % (
len(obj_cmp), obj_cmp.url(privacy=True)))
raise AssertionError("Target miscount %d != %d")
if self:
# Iterate over our expected entries inside of our object
for key, val in self.items():
@ -378,11 +389,19 @@ def test_plugin_email(mock_smtp, mock_smtpssl):
try:
if test_smtplib_exceptions is False:
# Verify we can acquire a target count as an integer
targets = len(obj)
# check that we're as expected
assert obj.notify(
title='test', body='body',
notify_type=NotifyType.INFO) == response
if response:
# If we successfully got a response, there must have
# been at least 1 target present
assert targets > 0
else:
for exception in test_smtplib_exceptions:
mock_socket.sendmail.side_effect = exception

View File

@ -288,9 +288,15 @@ def test_plugin_mailgun_attachments(mock_post):
'user1@example.com/user2@example.com?batch=yes'.format(apikey))
assert isinstance(obj, NotifyMailgun)
# objects will be combined into a single post in batch mode
assert len(obj) == 1
# Force our batch to break into separate messages
obj.default_batch_size = 1
# We'll send 2 messages
# We'll send 2 messages now
assert len(obj) == 2
mock_post.reset_mock()
assert obj.notify(

View File

@ -96,6 +96,8 @@ def test_plugin_mqtt_default_success(mqtt_client_mock):
obj = apprise.Apprise.instantiate(
'mqtt://localhost:1234/my/topic', suppress_exceptions=False)
assert isinstance(obj, NotifyMQTT)
# We only loaded 1 topic
assert len(obj) == 1
assert obj.url().startswith('mqtt://localhost:1234/my/topic')
# Verify default settings.
@ -135,6 +137,9 @@ def test_plugin_mqtt_multiple_topics_success(mqtt_client_mock):
'mqtt://localhost/my/topic,my/other/topic',
suppress_exceptions=False)
# Verify we have loaded 2 topics
assert len(obj) == 2
assert isinstance(obj, NotifyMQTT)
assert obj.url().startswith('mqtt://localhost')
assert re.search(r'my/topic', obj.url())

View File

@ -32,6 +32,7 @@
from apprise.plugins.NotifyOneSignal import NotifyOneSignal
from helpers import AppriseURLTester
from apprise import Apprise
# Disable logging for a cleaner testing output
import logging
@ -131,3 +132,119 @@ def test_plugin_onesignal_urls():
# Run our general tests
AppriseURLTester(tests=apprise_url_tests).run_all()
def test_plugin_onesignal_edge_cases():
"""
NotifyOneSignal() Batch Validation
"""
obj = Apprise.instantiate(
'onesignal://appid@apikey/#segment/@user/playerid/user@email.com'
'/?batch=yes')
# Validate that it loaded okay
assert isinstance(obj, NotifyOneSignal)
# all 4 types defined; but even in a batch mode, they can not be
# sent in one submission
assert len(obj) == 4
#
# Users
#
obj = Apprise.instantiate(
'onesignal://appid@apikey/@user1/@user2/@user3/@user4/?batch=yes')
assert isinstance(obj, NotifyOneSignal)
# We can lump these together - no problem
assert len(obj) == 1
# Same query, but no batch mode set
obj = Apprise.instantiate(
'onesignal://appid@apikey/@user1/@user2/@user3/@user4/?batch=no')
assert isinstance(obj, NotifyOneSignal)
# Individual queries
assert len(obj) == 4
#
# Segments
#
obj = Apprise.instantiate(
'onesignal://appid@apikey/#segment1/#seg2/#seg3/#seg4/?batch=yes')
assert isinstance(obj, NotifyOneSignal)
# We can lump these together - no problem
assert len(obj) == 1
# Same query, but no batch mode set
obj = Apprise.instantiate(
'onesignal://appid@apikey/#segment1/#seg2/#seg3/#seg4/?batch=no')
assert isinstance(obj, NotifyOneSignal)
# Individual queries
assert len(obj) == 4
#
# Player ID's
#
obj = Apprise.instantiate(
'onesignal://appid@apikey/pid1/pid2/pid3/pid4/?batch=yes')
assert isinstance(obj, NotifyOneSignal)
# We can lump these together - no problem
assert len(obj) == 1
# Same query, but no batch mode set
obj = Apprise.instantiate(
'onesignal://appid@apikey/pid1/pid2/pid3/pid4/?batch=no')
assert isinstance(obj, NotifyOneSignal)
# Individual queries
assert len(obj) == 4
#
# Emails
#
emails = ('abc@yahoo.ca', 'def@yahoo.ca', 'ghi@yahoo.ca', 'jkl@yahoo.ca')
obj = Apprise.instantiate(
'onesignal://appid@apikey/{}/?batch=yes'.format('/'.join(emails)))
assert isinstance(obj, NotifyOneSignal)
# We can lump these together - no problem
assert len(obj) == 1
# Same query, but no batch mode set
obj = Apprise.instantiate(
'onesignal://appid@apikey/{}/?batch=no'.format('/'.join(emails)))
assert isinstance(obj, NotifyOneSignal)
# Individual queries
assert len(obj) == 4
#
# Mixed
#
emails = ('abc@yahoo.ca', 'def@yahoo.ca', 'ghi@yahoo.ca', 'jkl@yahoo.ca')
users = ('@user1', '@user2', '@user3', '@user4')
players = ('player1', 'player2', 'player3', 'player4')
segments = ('#seg1', '#seg2', '#seg3', '#seg4')
path = '{}/{}/{}/{}'.format(
'/'.join(emails), '/'.join(users),
'/'.join(players), '/'.join(segments))
obj = Apprise.instantiate(
'onesignal://appid@apikey/{}/?batch=yes'.format(path))
assert isinstance(obj, NotifyOneSignal)
# We can lump these together - no problem
assert len(obj) == 4
# Same query, but no batch mode set
obj = Apprise.instantiate(
'onesignal://appid@apikey/{}/?batch=no'.format(path))
assert isinstance(obj, NotifyOneSignal)
# Individual queries
assert len(obj) == 16

View File

@ -101,8 +101,15 @@ apprise_url_tests = (
# an invalid entry (%20), and too short of an entry (a)
'instance': NotifyOpsgenie,
}),
('opsgenie://apikey/{}/@{}/#{}/*{}/^{}/'.format(
UUID4, UUID4, UUID4, UUID4, UUID4), {
('opsgenie://apikey/@{}/#{}/*{}/^{}/'.format(
UUID4, UUID4, UUID4, UUID4), {
# similar to the above, except we use the UUID's
'instance': NotifyOpsgenie,
}),
# Same link as before but @ missing at the front causing an ambigious
# lookup however the entry is treated a though a @ was in front (user)
('opsgenie://apikey/{}/#{}/*{}/^{}/'.format(
UUID4, UUID4, UUID4, UUID4), {
# similar to the above, except we use the UUID's
'instance': NotifyOpsgenie,
}),

View File

@ -299,8 +299,8 @@ def test_plugin_smseagle_edge_cases(mock_post):
aobj = Apprise()
assert aobj.add(
"smseagles://token@localhost:231/{}".format(target))
assert len(aobj) == 1
assert aobj.notify(title=title, body=body)
assert mock_post.call_count == 1
details = mock_post.call_args_list[0]
@ -315,8 +315,8 @@ def test_plugin_smseagle_edge_cases(mock_post):
assert aobj.add(
"smseagles://token@localhost:231/{}?status=Yes".format(
target))
assert len(aobj) == 1
assert aobj.notify(title=title, body=body)
assert mock_post.call_count == 1
details = mock_post.call_args_list[0]
@ -348,6 +348,8 @@ def test_plugin_smseagle_result_set(mock_post):
aobj.add(
'smseagle://token@10.0.0.112:8080/+12512222222/+12513333333/'
'12514444444?batch=yes')
# In a batch mode we can shove them all into 1 call
assert len(aobj[0]) == 1
assert aobj.notify(title=title, body=body)
@ -386,6 +388,7 @@ def test_plugin_smseagle_result_set(mock_post):
aobj.add(
'smseagle://token@10.0.0.112:8080/#group/Contact/'
'123456789?batch=no')
assert len(aobj[0]) == 3
assert aobj.notify(title=title, body=body)
@ -463,6 +466,8 @@ def test_plugin_smseagle_result_set(mock_post):
'smseagle://token@10.0.0.112:8080/513333333/#group1/@contact1/'
'contact2/12514444444?batch=yes')
# contacts and numbers can be combined and is calculated in batch response
assert len(aobj[0]) == 3
assert aobj.notify(title=title, body=body)
# There is a unique post to each (group, contact x2, and phone x2)

View File

@ -225,9 +225,15 @@ def test_plugin_smtp2go_attachments(mock_post):
'user1@example.com/user2@example.com?batch=yes'.format(apikey))
assert isinstance(obj, NotifySMTP2Go)
# objects will be combined into a single post in batch mode
assert len(obj) == 1
# Force our batch to break into separate messages
obj.default_batch_size = 1
# We'll send 2 messages
# We'll send 2 messages now
assert len(obj) == 2
mock_post.reset_mock()
assert obj.notify(

View File

@ -391,9 +391,14 @@ def test_plugin_sparkpost_attachments(mock_post):
'user1@example.com/user2@example.com?batch=yes'.format(apikey))
assert isinstance(obj, NotifySparkPost)
# As a batch mode, both emails can be lumped into 1
assert len(obj) == 1
# Force our batch to break into separate messages
obj.default_batch_size = 1
# We'll send 2 messages
# We'll send 2 messages no
assert len(obj) == 2
mock_post.reset_mock()
assert obj.notify(