URL parameters preserved for attachment queries (#187)

pull/189/head
Chris Caron 2019-12-15 13:17:58 -05:00 committed by GitHub
parent d1a15978af
commit 408810d6b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 22 deletions

View File

@ -28,6 +28,7 @@ import time
import mimetypes
from ..URLBase import URLBase
from ..utils import parse_bool
from ..AppriseLocale import gettext_lazy as _
class AttachBase(URLBase):
@ -61,7 +62,35 @@ class AttachBase(URLBase):
# 5 MB = 5242880 bytes
max_file_size = 5242880
def __init__(self, name=None, mimetype=None, cache=True, **kwargs):
# Here is where we define all of the arguments we accept on the url
# such as: schema://whatever/?overflow=upstream&format=text
# These act the same way as tokens except they are optional and/or
# have default values set if mandatory. This rule must be followed
template_args = {
'cache': {
'name': _('Cache Age'),
'type': 'int',
# We default to (600) which means we cache for 10 minutes
'default': 600,
},
'mime': {
'name': _('Forced Mime Type'),
'type': 'string',
},
'name': {
'name': _('Forced File Name'),
'type': 'string',
},
'verify': {
'name': _('Verify SSL'),
# SSL Certificate Authority Verification
'type': 'bool',
# Provide a default
'default': True,
},
}
def __init__(self, name=None, mimetype=None, cache=None, **kwargs):
"""
Initialize some general logging and common server arguments that will
keep things consistent when working with the configurations that
@ -109,19 +138,27 @@ class AttachBase(URLBase):
# Absolute path to attachment
self.download_path = None
# Set our cache flag; it can be True or a (positive) integer
try:
self.cache = cache if isinstance(cache, bool) else int(cache)
# Set our cache flag; it can be True, False, None, or a (positive)
# integer... nothing else
if cache is not None:
try:
self.cache = cache if isinstance(cache, bool) else int(cache)
except (TypeError, ValueError):
err = 'An invalid cache value ({}) was specified.'.format(
cache)
self.logger.warning(err)
raise TypeError(err)
# Some simple error checking
if self.cache < 0:
err = 'A negative cache value ({}) was specified.'.format(
cache)
self.logger.warning(err)
raise TypeError(err)
except (ValueError, TypeError):
err = 'An invalid cache value ({}) was specified.'.format(cache)
self.logger.warning(err)
raise TypeError(err)
else:
self.cache = None
# Validate mimetype if specified
if self._mimetype:
@ -211,12 +248,16 @@ class AttachBase(URLBase):
Simply returns true if the object has downloaded and stored the
attachment AND the attachment has not expired.
"""
cache = self.template_args['cache']['default'] \
if self.cache is None else self.cache
if self.download_path and os.path.isfile(self.download_path) \
and self.cache:
and cache:
# We have enough reason to look further into our cached content
# and verify it has not expired.
if self.cache is True:
if cache is True:
# return our fixed content as is; we will always cache it
return True
@ -224,7 +265,7 @@ class AttachBase(URLBase):
# content again.
try:
age_in_sec = time.time() - os.stat(self.download_path).st_mtime
if age_in_sec <= self.cache:
if age_in_sec <= cache:
return True
except (OSError, IOError):

View File

@ -78,6 +78,11 @@ class AttachHTTP(AttachBase):
# Where our content is written to upon a call to download.
self._temp_file = None
# Our Query String Dictionary; we use this to track arguments
# specified that aren't otherwise part of this class
self.qsd = {k: v for k, v in kwargs.get('qsd', {}).items()
if k not in self.template_args}
return
def download(self, **kwargs):
@ -122,6 +127,7 @@ class AttachHTTP(AttachBase):
url,
headers=headers,
auth=auth,
params=self.qsd,
verify=self.verify_certificate,
timeout=self.connection_timeout_sec,
stream=True) as r:
@ -252,18 +258,21 @@ class AttachHTTP(AttachBase):
Returns the URL built dynamically based on specified arguments.
"""
# Prepare our cache value
if isinstance(self.cache, bool) or not self.cache:
cache = 'yes' if self.cache else 'no'
else:
cache = int(self.cache)
# Define any arguments set
args = {
'verify': 'yes' if self.verify_certificate else 'no',
'cache': cache,
}
# Prepare our cache value
if self.cache is not None:
if isinstance(self.cache, bool) or not self.cache:
cache = 'yes' if self.cache else 'no'
else:
cache = int(self.cache)
# Set our cache value
args['cache'] = cache
if self._mimetype:
# A format was enforced
args['mime'] = self._mimetype
@ -275,6 +284,9 @@ class AttachHTTP(AttachBase):
# Append our headers into our args
args.update({'+{}'.format(k): v for k, v in self.headers.items()})
# Apply any remaining entries to our URL
args.update(self.qsd)
# Determine Authentication
auth = ''
if self.user and self.password:
@ -290,7 +302,7 @@ class AttachHTTP(AttachBase):
default_port = 443 if self.secure else 80
return '{schema}://{auth}{hostname}{port}{fullpath}/?{args}'.format(
return '{schema}://{auth}{hostname}{port}{fullpath}?{args}'.format(
schema=self.secure_protocol if self.secure else self.protocol,
auth=auth,
hostname=self.quote(self.host, safe=''),

View File

@ -78,14 +78,14 @@ def test_apprise_attachment():
assert aa
# Add another entry already in it's AttachBase format
response = AppriseAttachment.instantiate(path)
response = AppriseAttachment.instantiate(path, cache=True)
assert isinstance(response, AttachBase)
assert aa.add(response, asset=AppriseAsset())
# There is now 2 attachments
assert len(aa) == 2
# No cache set, so our cache defaults to True
# Cache is initialized to True
assert aa[1].cache is True
# Reset our object
@ -173,6 +173,10 @@ def test_apprise_attachment():
assert not aa.add(AppriseAttachment.instantiate(
'file://{}?name=andanother.png&cache=-600'.format(path)))
# Invalid cache value
assert not aa.add(AppriseAttachment.instantiate(
'file://{}?name=andanother.png'.format(path), cache='invalid'))
# No length change
assert len(aa) == 3

View File

@ -73,6 +73,39 @@ def test_attach_http_parse_url():
assert AttachHTTP.parse_url('http://') is None
def test_attach_http_query_string_dictionary():
"""
API: AttachHTTP() Query String Dictionary
"""
# no qsd specified
results = AttachHTTP.parse_url('http://localhost')
assert isinstance(results, dict)
# Create our object
obj = AttachHTTP(**results)
assert isinstance(obj, AttachHTTP)
assert re.search(r'[?&]verify=yes', obj.url())
# Now lets create a URL with a custom Query String entry
# some custom qsd entries specified
results = AttachHTTP.parse_url('http://localhost?dl=1&_var=test')
assert isinstance(results, dict)
# Create our object
obj = AttachHTTP(**results)
assert isinstance(obj, AttachHTTP)
assert re.search(r'[?&]verify=yes', obj.url())
# But now test that our custom arguments have also been set
assert re.search(r'[?&]dl=1', obj.url())
assert re.search(r'[?&]_var=test', obj.url())
@mock.patch('requests.get')
def test_attach_http(mock_get):
"""
@ -147,8 +180,26 @@ def test_attach_http(mock_get):
dummy_response = DummyResponse()
mock_get.return_value = dummy_response
# Test custom url get parameters
results = AttachHTTP.parse_url(
'http://user:pass@localhost/apprise.gif?+key=value')
'http://user:pass@localhost/apprise.gif?dl=1&cache=300')
assert isinstance(results, dict)
attachment = AttachHTTP(**results)
assert isinstance(attachment.url(), six.string_types) is True
# Test that our extended variables are passed along
assert mock_get.call_count == 0
assert attachment
assert mock_get.call_count == 1
assert 'params' in mock_get.call_args_list[0][1]
assert 'dl' in mock_get.call_args_list[0][1]['params']
# Verify that arguments that are reserved for apprise are not
# passed along
assert 'cache' not in mock_get.call_args_list[0][1]['params']
results = AttachHTTP.parse_url(
'http://user:pass@localhost/apprise.gif?+key=value&cache=True')
assert isinstance(results, dict)
attachment = AttachHTTP(**results)
assert isinstance(attachment.url(), six.string_types) is True