From bc4782005a57fb53260e69aa8135fc5162ac68da Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Fri, 31 May 2019 16:40:01 -0500 Subject: [PATCH 1/5] Adds support for template alias parameter --- pystmark.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pystmark.py b/pystmark.py index 24c6f0e..61cbee9 100644 --- a/pystmark.py +++ b/pystmark.py @@ -276,6 +276,7 @@ class Message(object): 'subject': 'Subject', 'tag': 'Tag', 'template_id': 'TemplateId', + 'template_alias': 'TemplateAlias', 'template_model': 'TemplateModel', 'html': 'HtmlBody', 'text': 'TextBody', @@ -297,9 +298,9 @@ class Message(object): _default_content_type = 'application/octet-stream' def __init__(self, sender=None, to=None, cc=None, bcc=None, subject=None, - template_id=None, template_model=None, tag=None, html=None, - text=None, reply_to=None, headers=None, attachments=None, - verify=False, track_opens=None): + template_id=None, template_alias=None, template_model=None, + tag=None, html=None, text=None, reply_to=None, headers=None, + attachments=None, verify=False, track_opens=None): self.sender = sender self.to = to self.cc = cc @@ -307,6 +308,7 @@ def __init__(self, sender=None, to=None, cc=None, bcc=None, subject=None, self.subject = subject self.tag = tag self.template_id = template_id + self.template_alias = template_alias self.template_model = template_model self.html = html self.text = text From 1b56660335a733cb25f33cf9ec101148f2438b14 Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Fri, 31 May 2019 16:50:59 -0500 Subject: [PATCH 2/5] Fixes test that compares fields to include template_alias --- tests/test_pystmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pystmark.py b/tests/test_pystmark.py index 897e7ad..91e2d75 100644 --- a/tests/test_pystmark.py +++ b/tests/test_pystmark.py @@ -363,7 +363,7 @@ def test_load_message_native(self): cc='dog,cat', bcc='foo,bar', subject='dogs', track_opens=True, headers=[dict(Name='Food', Value='7')], attachments=[], sender='admin', tag='tag', - template_id='template_id', template_model='template_model') + template_id='template_id', template_alias='template_alias', template_model='template_model') self.assertEqual(sorted(msg), sorted(Message._fields)) self.assertNotRaises(TypeError, Message.load_message, msg) self.assertNotRaises(MessageError, Message.load_message, msg, From 60e063baf5548a23c92f545aea601ef1faf11f20 Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Tue, 4 Jun 2019 11:40:47 -0500 Subject: [PATCH 3/5] Fixes pep8 linter errors tests seem to choke on --- pystmark.py | 385 ++++++++++++++++++++++++++-------------------------- 1 file changed, 195 insertions(+), 190 deletions(-) diff --git a/pystmark.py b/pystmark.py index 61cbee9..0dee345 100644 --- a/pystmark.py +++ b/pystmark.py @@ -1,4 +1,4 @@ -''' +""" pystmark -------- @@ -13,7 +13,7 @@ to provide. Optionally verify attachment size <=10MB Wrapper class for Message attachments and headers? -''' +""" from collections import Mapping from base64 import b64encode @@ -25,23 +25,23 @@ from _pystmark_meta import __title__, __version__, __license__ (__title__, __version__, __license__) # silence pyflakes -if sys.version_info[0] >= 3: # pragma: no cover +if sys.version_info[0] >= 3: # pragma: no cover from urllib.parse import urljoin + basestring = str def iteritems(obj): return obj.items() -else: # pragma: no cover +else: # pragma: no cover from urlparse import urljoin def iteritems(obj): return obj.iteritems() - -try: # pragma: no cover +try: # pragma: no cover import simplejson as json -except ImportError: # pragma: no cover +except ImportError: # pragma: no cover import json # Constant defined in the Postmark docs: @@ -75,12 +75,11 @@ def iteritems(obj): 'InboundError': 100008 } - -''' Simple API ''' +""" Simple API """ def send(message, api_key=None, secure=None, test=None, **request_args): - '''Send a message. + """Send a message. :param message: Message to send. :type message: `dict` or :class:`Message` @@ -88,10 +87,10 @@ def send(message, api_key=None, secure=None, test=None, **request_args): :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`SendResponse` - ''' + """ return _default_pyst_sender.send(message=message, api_key=api_key, secure=secure, test=test, **request_args) @@ -101,7 +100,7 @@ def send_with_template(message, secure=None, test=None, **request_args): - '''Send a message. + """Send a message. :param message: Message to send. :type message: `dict` or :class:`Message` @@ -109,10 +108,10 @@ def send_with_template(message, :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`SendResponse` - ''' + """ return _default_pyst_template_sender.send(message=message, api_key=api_key, secure=secure, @@ -121,7 +120,7 @@ def send_with_template(message, def send_batch(messages, api_key=None, secure=None, test=None, **request_args): - '''Send a batch of messages. + """Send a batch of messages. :param messages: Messages to send. :type message: A list of `dict` or :class:`Message` @@ -129,117 +128,117 @@ def send_batch(messages, api_key=None, secure=None, test=None, **request_args): :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`BatchSendResponse` - ''' + """ return _default_pyst_batch_sender.send(messages=messages, api_key=api_key, secure=secure, test=test, **request_args) def get_delivery_stats(api_key=None, secure=None, test=None, **request_args): - '''Get delivery stats for your Postmark account. + """Get delivery stats for your Postmark account. :param api_key: Your Postmark API key. Required, if `test` is not `True`. :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`DeliveryStatsResponse` - ''' + """ return _default_delivery_stats.get(api_key=api_key, secure=secure, test=test, **request_args) def get_bounces(api_key=None, secure=None, test=None, **request_args): - '''Get a paginated list of bounces. + """Get a paginated list of bounces. :param api_key: Your Postmark API key. Required, if `test` is not `True`. :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`BouncesResponse` - ''' + """ return _default_bounces.get(api_key=api_key, secure=secure, test=test, **request_args) def get_bounce(bounce_id, api_key=None, secure=None, test=None, **request_args): - '''Get a single bounce. + """Get a single bounce. :param bounce_id: The bounce's id. Get the id with :func:`get_bounces`. :param api_key: Your Postmark API key. Required, if `test` is not `True`. :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`BounceResponse` - ''' + """ return _default_bounce.get(bounce_id, api_key=api_key, secure=secure, test=test, **request_args) def get_bounce_dump(bounce_id, api_key=None, secure=None, test=None, **request_args): - '''Get the raw email dump for a single bounce. + """Get the raw email dump for a single bounce. :param bounce_id: The bounce's id. Get the id with :func:`get_bounces`. :param api_key: Your Postmark API key. Required, if `test` is not `True`. :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`BounceDumpResponse` - ''' + """ return _default_bounce_dump.get(bounce_id, api_key=api_key, secure=secure, test=test, **request_args) def get_bounce_tags(api_key=None, secure=None, test=None, **request_args): - '''Get a list of tags for bounces associated with your Postmark server. + """Get a list of tags for bounces associated with your Postmark server. :param api_key: Your Postmark API key. Required, if `test` is not `True`. :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`BounceTagsResponse` - ''' + """ return _default_bounce_tags.get(api_key=api_key, secure=secure, test=test, **request_args) def activate_bounce(bounce_id, api_key=None, secure=None, test=None, **request_args): - '''Activate a deactivated bounce. + """Activate a deactivated bounce. :param bounce_id: The bounce's id. Get the id with :func:`get_bounces`. :param api_key: Your Postmark API key. Required, if `test` is not `True`. :param secure: Use the https scheme for the Postmark API. Defaults to `True` :param test: Use the Postmark Test API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`BounceActivateResponse` - ''' + """ return _default_bounce_activate.activate(bounce_id, api_key=api_key, secure=secure, test=test, **request_args) -''' Messages ''' +""" Messages """ class Message(object): - ''' A container for message(s) to send to the Postmark API. + """ A container for message(s) to send to the Postmark API. You can populate this message with defaults for initializing an :class:`Interface`. The message will be combined with the final message and verified before transmission. @@ -266,7 +265,7 @@ class Message(object): :param verify: Verify the message when initialized. Defaults to `False`. :param track_opens: Set to true to enable tracking email opens. - ''' + """ _fields = { 'to': 'To', @@ -321,10 +320,10 @@ def __init__(self, sender=None, to=None, cc=None, bcc=None, subject=None, self.verify() def data(self): - '''Returns data formatted for a POST request to the Postmark send API. + """Returns data formatted for a POST request to the Postmark send API. :rtype: `dict` - ''' + """ d = {} for val, key in self._fields.items(): val = getattr(self, val) @@ -333,21 +332,21 @@ def data(self): return d def json(self): - '''Return json-encoded string of message data. + """Return json-encoded string of message data. :rtype: `str` - ''' + """ return json.dumps(self.data(), ensure_ascii=True) @classmethod def load_message(self, message, **kwargs): - '''Create a :class:`Message` from a message data `dict`. + """Create a :class:`Message` from a message data `dict`. :param message: A `dict` of message data. - :param \*\*kwargs: Additional keyword arguments to construct + :param kwargs: Additional keyword arguments to construct :class:`Message` with. :rtype: :class:`Message` - ''' + """ kwargs.update(message) message = kwargs try: @@ -361,16 +360,16 @@ def load_message(self, message, **kwargs): return message def load_from(self, other, **kwargs): - '''Create a :class:`Message` by merging `other` with `self`. + """Create a :class:`Message` by merging `other` with `self`. Values from `other` will be copied to `self` if the value was not set on `self` and is set on `other`. :param other: The :class:`Message` to copy defaults from. :type other: :class:`Message` - :param \*\*kwargs: Additional keyword arguments to construct + :param kwargs: Additional keyword arguments to construct :class:`Message` with. :rtype: :class:`Message` - ''' + """ data = self.data() other_data = other.data() for k, v in iteritems(other_data): @@ -379,18 +378,18 @@ def load_from(self, other, **kwargs): return self.load_message(data, **kwargs) def add_header(self, name, value): - '''Attach an email header to send with the message. + """Attach an email header to send with the message. :param name: The name of the header value. :param value: The header value. - ''' + """ if self.headers is None: self.headers = [] self.headers.append(dict(Name=name, Value=value)) def attach_binary(self, data, filename, content_type=None, content_id=None): - '''Attach a file to the message given raw binary data. + """Attach a file to the message given raw binary data. :param data: Raw data to attach to the message. :param filename: Name of the file for the data. @@ -399,7 +398,7 @@ def attach_binary(self, data, filename, content_type=None, :param content_id: ContentID URL of the attachment. A RFC 2392- compliant URL for the attachment that allows it to be referenced from inside the body of the message. Must start with 'cid:' - ''' + """ if self.attachments is None: self.attachments = [] if content_type is None: @@ -419,7 +418,7 @@ def attach_binary(self, data, filename, content_type=None, def attach_file(self, filename, content_type=None, content_id=None): - '''Attach a file to the message given a filename. + """Attach a file to the message given a filename. :param filename: Name of the file to attach. :param content_type: mimetype of the data. It will be guessed from the @@ -427,7 +426,7 @@ def attach_file(self, filename, content_type=None, :param content_id: ContentID URL of the attachment. A RFC 2392- compliant URL for the attachment that allows it to be referenced from inside the body of the message. Must start with 'cid:' - ''' + """ # Open the file, grab the filename, detect content type name = os.path.basename(filename) if not name: @@ -439,11 +438,11 @@ def attach_file(self, filename, content_type=None, content_id=content_id) def verify(self): - '''Verifies the message data based on rules and restrictions defined + """Verifies the message data based on rules and restrictions defined in the Postmark API docs. There can be no more than 20 recipients in total. NOTE: This does not check that your attachments total less than 10MB, you must do that yourself. - ''' + """ if self.to is None: raise MessageError('"to" is required') if self.html is None and self.text is None: @@ -458,75 +457,75 @@ def verify(self): @property def recipients(self): - '''A list of all recipients for this message. - ''' + """A list of all recipients for this message. + """ cc = self._cc or [] bcc = self._bcc or [] return self._to + cc + bcc @property def to(self): - '''A comma delimited string of receivers for the message 'To' + """A comma delimited string of receivers for the message 'To' field. - ''' + """ if self._to is not None: return ','.join(self._to) @to.setter def to(self, to): - ''' + """ :param to: Email addresses for the 'To' API field. :type to: :keyword:`list` or `str` - ''' + """ if isinstance(to, basestring): to = to.split(',') self._to = to @property def cc(self): - '''A comma delimited string of receivers for the message 'Cc' + """A comma delimited string of receivers for the message 'Cc' field. - ''' + """ if self._cc is not None: return ','.join(self._cc) @cc.setter def cc(self, cc): - ''' + """ :param cc: Email addresses for the 'Cc' API field. :type cc: :keyword:`list` or `str` - ''' + """ if isinstance(cc, basestring): cc = cc.split(',') self._cc = cc @property def bcc(self): - '''A comma delimited string of receivers for the message 'Bcc' + """A comma delimited string of receivers for the message 'Bcc' field. - ''' + """ if self._bcc is not None: return ','.join(self._bcc) @bcc.setter def bcc(self, bcc): - ''' + """ :param bcc: Email addresses for the 'Bcc' API field. :type bcc: :keyword:`list` or `str` - ''' + """ if isinstance(bcc, basestring): bcc = bcc.split(',') self._bcc = bcc @classmethod def _convert_postmark_to_native(cls, message): - '''Converts Postmark message API field names to their corresponding + """Converts Postmark message API field names to their corresponding :class:`Message` attribute names. :param message: Postmark message data, with API fields using Postmark API names. :type message: `dict` - ''' + """ d = {} for dest, src in cls._fields.items(): if src in message: @@ -534,10 +533,10 @@ def _convert_postmark_to_native(cls, message): return d def _detect_content_type(self, filename): - '''Determine the mimetype for a file. + """Determine the mimetype for a file. :param filename: Filename of file to detect. - ''' + """ name, ext = os.path.splitext(filename) if not ext: raise MessageError('File requires an extension.') @@ -550,30 +549,30 @@ def _detect_content_type(self, filename): return mimetypes.types_map.get(ext, self._default_content_type) def _verify_headers(self): - '''Verify that header values match the format expected by the Postmark + """Verify that header values match the format expected by the Postmark API. - ''' + """ if self.headers is None: return self._verify_dict_list(self.headers, ('Name', 'Value'), 'Header') def _verify_attachments(self): - '''Verify that attachment values match the format expected by the + """Verify that attachment values match the format expected by the Postmark API. - ''' + """ if self.attachments is None: return keys = ('Name', 'Content', 'ContentType') self._verify_dict_list(self.attachments, keys, 'Attachment') def _verify_dict_list(self, values, keys, name): - '''Validate a list of `dict`, ensuring it has specific keys + """Validate a list of `dict`, ensuring it has specific keys and no others. :param values: A list of `dict` to validate. :param keys: A list of keys to validate each `dict` against. :param name: Name describing the values, to show in error messages. - ''' + """ keys = set(keys) name = name.title() for value in values: @@ -590,9 +589,9 @@ def _verify_dict_list(self, values, keys, name): raise MessageError(err.format(name, words)) def __eq__(self, other): - '''If comparing to a `dict`, convert to a :class:`Message` + """If comparing to a `dict`, convert to a :class:`Message` then compare data fields. - ''' + """ if isinstance(other, Mapping): other = self.__class__.load_message(other) return self.data() == other.data() @@ -602,13 +601,14 @@ def __ne__(self, other): class BouncedMessage(object): - '''Bounced message data wrapper. + """Bounced message data wrapper. :param bounce_data: Raw bounced message data retrieved from :class:`Bounce` or :class:`Bounces`. :param sender: The :class:`Interface` that made the request for the bounce data. Defaults to `None`. - ''' + """ + def __init__(self, bounce_data, sender=None): self._data = bounce_data self._sender = sender @@ -626,13 +626,13 @@ def __init__(self, bounce_data, sender=None): self.subject = bounce_data['Subject'] def dump(self, sender=None, **kwargs): - '''Retrieve raw email dump for this bounce. + """Retrieve raw email dump for this bounce. :param sender: A :class:`BounceDump` object to get dump with. Defaults to `None`. - :param \*\*kwargs: Keyword arguments passed to + :param kwargs: Keyword arguments passed to :func:`requests.request`. - ''' + """ if sender is None: if self._sender is None: sender = _default_bounce_dump @@ -644,10 +644,10 @@ def dump(self, sender=None, **kwargs): class MessageConfirmation(object): - '''Wrapper around data returned from Postmark after sending + """Wrapper around data returned from Postmark after sending :param data: Data returned from Postmark upon sending a message - ''' + """ def __init__(self, data): self._data = data @@ -661,22 +661,23 @@ def __init__(self, data): class BounceTypeData(object): - '''Bounce type data wrapper. + """Bounce type data wrapper. :param bounce_type_data: Raw bounce type data retrieved from :class:`DeliveryStats`. - ''' + """ + def __init__(self, bounce_type_data): self.count = bounce_type_data.get('Count', 0) self.name = bounce_type_data['Name'] self.type = bounce_type_data.get('Type', 'All') -''' Response Wrappers ''' +""" Response Wrappers """ class Response(object): - '''Base class for API response wrappers. The wrapped + """Base class for API response wrappers. The wrapped :class:`requests.Response` object interface is exposed by this class, unless the attribute is defined in `self._attrs`. @@ -685,7 +686,7 @@ class Response(object): :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ _attrs = [] def __init__(self, response, sender=None): @@ -700,9 +701,9 @@ def __init__(self, response, sender=None): self._requests_response = response def __getattribute__(self, k): - '''Gets attribute from `self` if attribute key is in `self._attrs`, + """Gets attribute from `self` if attribute key is in `self._attrs`, else get it from the wrapped :class:`requests.Response`. - ''' + """ if k == '_attrs' or k in object.__getattribute__(self, '_attrs'): return object.__getattribute__(self, k) r = object.__getattribute__(self, '_requests_response') @@ -711,16 +712,16 @@ def __getattribute__(self, k): return r.__getattribute__(k) def __setattr__(self, k, v): - '''Sets attribute on `self` if attribute key is in `self._attrs`, + """Sets attribute on `self` if attribute key is in `self._attrs`, else sets it on the wrapped :class:`requests.Response`. - ''' + """ if k in ['_attrs', '_requests_response'] or k in self._attrs: object.__setattr__(self, k, v) else: self._requests_response.__setattr__(k, v) def raise_for_status(self): - '''Raise Postmark-specific HTTP errors. If there isn't one, the + """Raise Postmark-specific HTTP errors. If there isn't one, the standard HTTP error is raised. HTTP 401 raises :class:`UnauthorizedError` @@ -728,7 +729,7 @@ def raise_for_status(self): HTTP 422 raises :class:`UnprocessableEntityError` HTTP 500 raises :class:`InternalServerError` - ''' + """ if self.status_code == 401: raise UnauthorizedError(self._requests_response) elif self.status_code == 422: @@ -739,14 +740,14 @@ def raise_for_status(self): class SendResponse(Response): - '''Wrapper around :func:`Sender.send` and :func:`BatchSender.send` + """Wrapper around :func:`Sender.send` and :func:`BatchSender.send` :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ _attrs = ['message', 'raise_for_status'] def __init__(self, response, sender=None): @@ -756,14 +757,14 @@ def __init__(self, response, sender=None): class BatchSendResponse(Response): - '''Wrapper around :func:`Sender.send` and :func:`BatchSender.send` + """Wrapper around :func:`Sender.send` and :func:`BatchSender.send` :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ _attrs = ['messages', 'raise_for_status'] def __init__(self, response, sender=None): @@ -773,14 +774,14 @@ def __init__(self, response, sender=None): class BouncesResponse(Response): - '''Wrapper for responses from :func:`Bounces.get`. + """Wrapper for responses from :func:`Bounces.get`. :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ _attrs = ['bounces', 'total'] def __init__(self, response, sender=None): @@ -793,14 +794,14 @@ def __init__(self, response, sender=None): class BounceResponse(Response): - '''Wrapper for responses from :func:`Bounce.get`. + """Wrapper for responses from :func:`Bounce.get`. :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ _attrs = ['bounce'] def __init__(self, response, sender=None): @@ -812,14 +813,15 @@ def __init__(self, response, sender=None): class BounceDumpResponse(Response): - '''Wrapper for responses from :func:`BounceDump.get`. + """Wrapper for responses from :func:`BounceDump.get`. :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ + def __init__(self, response, sender=None): super(BounceDumpResponse, self).__init__(response, sender=sender) data = self._data or {} @@ -827,28 +829,30 @@ def __init__(self, response, sender=None): class BounceTagsResponse(Response): - '''Wrapper for responses from :func:`BounceTags.get`. + """Wrapper for responses from :func:`BounceTags.get`. :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ + def __init__(self, response, sender=None): super(BounceTagsResponse, self).__init__(response, sender=sender) self.tags = self._data or [] class DeliveryStatsResponse(Response): - '''Wrapper for responses from :func:`BounceActivate.activate`. + """Wrapper for responses from :func:`BounceActivate.activate`. :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ + def __init__(self, response, sender=None): super(DeliveryStatsResponse, self).__init__(response, sender=sender) data = self._data or {} @@ -864,14 +868,15 @@ def __init__(self, response, sender=None): class BounceActivateResponse(Response): - '''Wrapper for responses from the bounce activate endpoint. + """Wrapper for responses from the bounce activate endpoint. :param response: Response returned from :func:`requests.request`. :type response: :class:`requests.Response` :param sender: The API interface wrapper that generated the request. Defaults to `None`. :type sender: :class:`Interface` - ''' + """ + def __init__(self, response, sender=None): super(BounceActivateResponse, self).__init__(response, sender=sender) data = self._data or {} @@ -883,17 +888,17 @@ def __init__(self, response, sender=None): self.bounce = BouncedMessage(data['Bounce'], sender=sender) -''' Interfaces ''' +""" Interfaces """ class Interface(object): - '''Base class interface for Postmark API endpoint wrappers + """Base class interface for Postmark API endpoint wrappers :param api_key: Your Postmark API key. Defaults to `None`. :param secure: Use the https scheme for API requests. Defaults to `True`. :param test: Use the Postmark test API. Defaults to `False`. - ''' + """ method = None endpoint = None @@ -911,25 +916,25 @@ def __init__(self, api_key=None, secure=True, test=False): self.test = test def _request(self, url, **kwargs): - '''Inner :func:`requests.request` wrapper. + """Inner :func:`requests.request` wrapper. :param url: Endpoint url - :param \*\*kwargs: Keyword arguments to pass to + :param kwargs: Keyword arguments to pass to :func:`requests.request`. - ''' + """ if self.method is None: raise NotImplementedError('method must be defined on a subclass') response = requests.request(self.method, url, **kwargs) return self.response_class(response, sender=self) def _get_api_url(self, secure=None, **formatters): - '''Constructs Postmark API url + """Constructs Postmark API url :param secure: Use the https Postmark API. - :param \*\*formatters: :func:`string.format` keyword arguments to + :param formatters: :func:`string.format` keyword arguments to format the url with. :rtype: Postmark API url - ''' + """ if self.endpoint is None: raise NotImplementedError('endpoint must be defined on a subclass') if secure is None: @@ -944,14 +949,14 @@ def _get_api_url(self, secure=None, **formatters): return url def _get_headers(self, api_key=None, test=None, request_args=None): - '''Constructs the headers to use for the request. + """Constructs the headers to use for the request. :param api_key: Your Postmark API key. Defaults to `None`. :param test: Use the Postmark test API. Defaults to `self.test`. :param request_args: Keyword args to pass to :func:`requests.request`. Defaults to `None`. :rtype: `dict` of header values. - ''' + """ if request_args is None: request_args = {} headers = {} @@ -969,32 +974,32 @@ def _get_headers(self, api_key=None, test=None, request_args=None): class GetInterface(Interface): - '''Base interface class for Postmark API endpoints that use GET''' + """Base interface class for Postmark API endpoints that use GET""" method = 'GET' def get(self, api_key=None, secure=None, test=None, **request_args): - '''Make a GET request to the Postmark API + """Make a GET request to the Postmark API :param api_key: Your Postmark API key. :param secure: Use the https scheme for Postmark API. Defaults to `True` :param test: Make a test request to the Postmark API. Defaults to `False`. - :param \*\*request_args: Keyword arguments to pass to + :param request_args: Keyword arguments to pass to :func:`requests.request`. :rtype: :class:`Response` - ''' + """ url = self._get_api_url(secure=secure) headers = self._get_headers(api_key=api_key, test=test, request_args=request_args) return self._request(url, headers=headers, **request_args) -''' Send API ''' +""" Send API """ class Sender(Interface): - '''Sends a single message via the Postmark API. + """Sends a single message via the Postmark API. All of the arguments used in constructing this object are used as defaults in the final call to :meth:`Sender.send`. @@ -1007,7 +1012,7 @@ class Sender(Interface): Defaults to `True` :param test: Make a test request to the Postmark API. Defaults to `False`. - ''' + """ method = 'POST' endpoint = '/email' @@ -1019,7 +1024,7 @@ def __init__(self, message=None, api_key=None, secure=True, test=False): def send(self, message=None, api_key=None, secure=None, test=None, **request_args): - '''Send request to Postmark API. + """Send request to Postmark API. Returns result of :func:`requests.post`. :param message: Your Postmark message data. @@ -1028,9 +1033,9 @@ def send(self, message=None, api_key=None, secure=None, test=None, :type api_key: `str` :param test: Make a test request to the Postmark API. :param secure: Use the https Postmark API. - :param \*\*request_args: Passed to :func:`requests.post` + :param request_args: Passed to :func:`requests.post` :rtype: :class:`requests.Response` - ''' + """ headers = self._get_headers(api_key=api_key, test=test, request_args=request_args) data = self._get_request_content(message) @@ -1038,7 +1043,7 @@ def send(self, message=None, api_key=None, secure=None, test=None, return self._request(url, data=data, headers=headers, **request_args) def _load_initial_message(self, message=None): - '''Converts message to :class:`Message` and sets it on `self`''' + """Converts message to :class:`Message` and sets it on `self`""" if message is None: message = Message(verify=False) if isinstance(message, Mapping): @@ -1046,12 +1051,12 @@ def _load_initial_message(self, message=None): self.message = message def _cast_message(self, message=None): - '''Convert message data to :class:`Message` if needed, and + """Convert message data to :class:`Message` if needed, and merge with the default message. :param message: Message to merge with the default message. :rtype: :class:`Message` - ''' + """ if message is None: message = {} if isinstance(message, Mapping): @@ -1059,18 +1064,18 @@ def _cast_message(self, message=None): return message.load_from(self.message, verify=True) def _get_request_content(self, message=None): - '''Updates message with default message paramaters. + """Updates message with default message paramaters. :param message: Postmark message data :type message: `dict` :rtype: JSON encoded `unicode` - ''' + """ message = self._cast_message(message=message) return message.json() class TemplateSender(Sender): - '''Sends a single message via the Postmark API with template. + """Sends a single message via the Postmark API with template. All of the arguments used in constructing this object are used as defaults in the final call to :meth:`Sender.send`. @@ -1083,13 +1088,13 @@ class TemplateSender(Sender): Defaults to `True` :param test: Make a test request to the Postmark API. Defaults to `False`. - ''' + """ endpoint = '/email/withTemplate' class BatchSender(Sender): - '''Sends a batch of messages via the Postmark API. + """Sends a batch of messages via the Postmark API. All of the arguments used in constructing this object are used as defaults in the final call to :meth:`BatchSender.send`. @@ -1102,14 +1107,14 @@ class BatchSender(Sender): Defaults to `True` :param test: Make a test request to the Postmark API. Defaults to `False`. - ''' + """ endpoint = '/email/batch' response_class = BatchSendResponse def send(self, messages=None, api_key=None, secure=None, test=None, **request_args): - '''Send batch request to Postmark API. + """Send batch request to Postmark API. Returns result of :func:`requests.post`. :param messages: Batch messages to send to the Postmark API. @@ -1118,21 +1123,21 @@ def send(self, messages=None, api_key=None, secure=None, test=None, :param test: Make a test request to the Postmark API. Defaults to `self.test`. :param secure: Use the https Postmark API. Defaults to `self.secure`. - :param \*\*request_args: Passed to :func:`requests.request` + :param request_args: Passed to :func:`requests.request` :rtype: :class:`BatchSendResponse` - ''' + """ return super(BatchSender, self).send(message=messages, test=test, api_key=api_key, secure=secure, **request_args) def _get_request_content(self, message=None): - '''Updates all messages in message with default message + """Updates all messages in message with default message parameters. :param message: A collection of Postmark message data :type message: a collection of message `dict`s :rtype: JSON encoded `str` - ''' + """ if not message: raise MessageError('No messages to send.') if len(message) > MAX_BATCH_MESSAGES: @@ -1143,18 +1148,18 @@ def _get_request_content(self, message=None): return json.dumps(message, ensure_ascii=True) -''' Bounce API ''' +""" Bounce API """ class Bounces(GetInterface): - '''Multiple bounce retrieval endpoint wrapper. + """Multiple bounce retrieval endpoint wrapper. :param api_key: Your Postmark API key. Defaults to `None`. :param secure: Use the https scheme for Postmark API. Defaults to `True`. :param test: Make a test request to the Postmark API. Defaults to `False`. - ''' + """ endpoint = '/bounces' response_class = BouncesResponse @@ -1166,7 +1171,7 @@ def __init__(self, api_key=None, secure=True, test=False): def get(self, bounce_type=None, inactive=None, email_filter=None, message_id=None, count=None, offset=None, api_key=None, secure=None, test=None, **request_args): - '''Builds query string params from inputs. It handles offset and + """Builds query string params from inputs. It handles offset and count defaults and validation. :param bounce_type: The type of bounces retrieve. See `bounce_types` @@ -1187,7 +1192,7 @@ def get(self, bounce_type=None, inactive=None, email_filter=None, Defaults to `self.secure`. :params test: Use the Postmark test API. Defaults to `self.test`. :rtype: :class:`BouncesResponse` - ''' + """ params = self._construct_params(bounce_type=bounce_type, inactive=inactive, email_filter=email_filter, @@ -1202,11 +1207,11 @@ def get(self, bounce_type=None, inactive=None, email_filter=None, return response def _request(self, url, **kwargs): - '''Makes request to :func:`Interface.request` and caches it. + """Makes request to :func:`Interface.request` and caches it. :param url: endpoint url - :params \*\*kwargs: kwargs to pass to :func:`requests.request` - ''' + :params kwargs: kwargs to pass to :func:`requests.request` + """ response = super(Bounces, self)._request(url, **kwargs) self._last_response = response return response @@ -1214,7 +1219,7 @@ def _request(self, url, **kwargs): def _construct_params(self, bounce_type=None, inactive=None, email_filter=None, message_id=None, count=None, offset=None): - '''Builds query string params from inputs. It handles offset and + """Builds query string params from inputs. It handles offset and count defaults and validation. :param bounce_type: The type of bounces retrieve. See `bounce_types` @@ -1230,7 +1235,7 @@ def _construct_params(self, bounce_type=None, inactive=None, Defaults to 25 if `message_id` is not provided. :param offset: The page offset for bounces to retrieve. Defaults to 0 if `message_id` is not provided. - ''' + """ params = {} if bounce_type is not None: if bounce_type not in bounce_types: @@ -1259,14 +1264,14 @@ def _construct_params(self, bounce_type=None, inactive=None, class Bounce(GetInterface): - '''Single bounce retrieval endpoint wrapper. + """Single bounce retrieval endpoint wrapper. :param api_key: Your Postmark API key. Defaults to `None`. :param secure: Use the https scheme for Postmark API. Defaults to `True`. :param test: Make a test request to the Postmark API. Defaults to `False`. - ''' + """ endpoint = '/bounces/{bounce_id}' response_class = BounceResponse @@ -1275,7 +1280,7 @@ def __init__(self, api_key=None, secure=True, test=False): def get(self, bounce_id, api_key=None, secure=None, test=None, **request_args): - '''Retrieves a single bounce's data. + """Retrieves a single bounce's data. :param bounce_id: A bounce's ID retrieved with :class:`Bounces`. :param api_key: Your Postmark API key. Defaults to `self.api_key`. @@ -1283,10 +1288,10 @@ def get(self, bounce_id, api_key=None, secure=None, test=None, Defaults to `self.secure`. :param test: Make a test request to the Postmark API. Defaults to `self.test`. - :param \*\*request_args: Keyword args to pass to + :param request_args: Keyword args to pass to :func:`requests.request`. :rtype: :class:`BounceResponse` - ''' + """ url = self._get_api_url(secure=secure, bounce_id=bounce_id) headers = self._get_headers(api_key=api_key, test=test, request_args=request_args) @@ -1294,25 +1299,25 @@ def get(self, bounce_id, api_key=None, secure=None, test=None, class BounceDump(Bounce): - '''Bounce dump endpoint wrapper.''' + """Bounce dump endpoint wrapper.""" response_class = BounceDumpResponse endpoint = '/bounces/{bounce_id}/dump' class BounceTags(GetInterface): - '''Bounce tags endpoint wrapper.''' + """Bounce tags endpoint wrapper.""" response_class = BounceTagsResponse endpoint = '/bounces/tags' class DeliveryStats(GetInterface): - '''Delivery Stats endpoint wrapper.''' + """Delivery Stats endpoint wrapper.""" response_class = DeliveryStatsResponse endpoint = '/deliverystats' class BounceActivate(Interface): - '''Bounce Activation endpoint wrapper. + """Bounce Activation endpoint wrapper. :param bounce_id: A bounce's ID retrieved with :class:`Bounces`. Defaults to `None`. @@ -1321,7 +1326,7 @@ class BounceActivate(Interface): Defaults to `True`. :param test: Make a test request to the Postmark API. Defaults to `False`. - ''' + """ response_class = BounceActivateResponse method = 'PUT' endpoint = '/bounces/{bounce_id}/activate' @@ -1332,7 +1337,7 @@ def __init__(self, api_key=None, secure=True, test=False): def activate(self, bounce_id, api_key=None, secure=None, test=None, **request_args): - '''Activates a bounce. + """Activates a bounce. :param bounce_id: A bounce's ID retrieved with :class:`Bounces`. :param api_key: Your Postmark API key. Defaults to `self.api_key`. @@ -1340,25 +1345,25 @@ def activate(self, bounce_id, api_key=None, secure=None, test=None, Defaults to `self.secure`. :param test: Make a test request to the Postmark API. Defaults to `self.test`. - :param \*\*request_args: Keyword args to pass to + :param request_args: Keyword args to pass to :func:`requests.request`. :rtype: :class:`BounceActivateResponse` - ''' + """ url = self._get_api_url(secure=secure, bounce_id=bounce_id) headers = self._get_headers(api_key=api_key, test=test, request_args=request_args) return self._request(url, headers=headers, **request_args) -''' Exceptions ''' +""" Exceptions """ class PystmarkError(Exception): - '''Base `Exception` for :mod:`pystmark` errors. + """Base `Exception` for :mod:`pystmark` errors. :param message: Message to raise with the Exception. Defaults to `None`. - ''' + """ message = '' def __init__(self, message=None): @@ -1370,24 +1375,24 @@ def __str__(self): class MessageError(PystmarkError): - ''' Raised when a message meant to be sent to Postmark API looks + """ Raised when a message meant to be sent to Postmark API looks malformed - ''' + """ message = 'Refusing to send malformed message' class BounceError(PystmarkError): - ''' Raised when a bounce API method fails ''' + """ Raised when a bounce API method fails """ message = 'Bounce API failure' class ResponseError(PystmarkError): - '''Base `Exception` for errors received from Postmark API + """Base `Exception` for errors received from Postmark API :param response: A :class:`Response`. :param message: Message to raise with the Exception. Defaults to `None`. - ''' + """ def __init__(self, response, message=None): self.response = response @@ -1411,28 +1416,28 @@ def __str__(self): class UnauthorizedError(ResponseError): - '''Raised when Postmark responds with a :attr:`status_code` of 401 + """Raised when Postmark responds with a :attr:`status_code` of 401 Indicates a missing or incorrect API key. - ''' + """ pass class UnprocessableEntityError(ResponseError): - '''Raised when Postmark responds with a :attr:`status_code` of 422. + """Raised when Postmark responds with a :attr:`status_code` of 422. Indicates message(s) received by Postmark were malformed. - ''' + """ pass class InternalServerError(ResponseError): - '''Raised when Postmark responds with a :attr:`status_code` of 500 + """Raised when Postmark responds with a :attr:`status_code` of 500 Indicates an error on Postmark's end. Any messages sent in the request were not received by them. - ''' + """ pass -''' Singletons ''' +""" Singletons """ _default_pyst_sender = Sender() _default_pyst_template_sender = TemplateSender() From b165e1df235faf16b69c663439ebceb0f8358b2b Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Tue, 4 Jun 2019 11:44:42 -0500 Subject: [PATCH 4/5] Removes blank line that linter interprets as too long --- pystmark.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pystmark.py b/pystmark.py index 0dee345..afb52ed 100644 --- a/pystmark.py +++ b/pystmark.py @@ -363,7 +363,6 @@ def load_from(self, other, **kwargs): """Create a :class:`Message` by merging `other` with `self`. Values from `other` will be copied to `self` if the value was not set on `self` and is set on `other`. - :param other: The :class:`Message` to copy defaults from. :type other: :class:`Message` :param kwargs: Additional keyword arguments to construct From 549685a687c0b1b3badbfe65c4b63b222bc6abab Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Tue, 4 Jun 2019 11:53:52 -0500 Subject: [PATCH 5/5] Fixes line too long in tests --- tests/test_pystmark.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pystmark.py b/tests/test_pystmark.py index 91e2d75..396c4a7 100644 --- a/tests/test_pystmark.py +++ b/tests/test_pystmark.py @@ -363,7 +363,8 @@ def test_load_message_native(self): cc='dog,cat', bcc='foo,bar', subject='dogs', track_opens=True, headers=[dict(Name='Food', Value='7')], attachments=[], sender='admin', tag='tag', - template_id='template_id', template_alias='template_alias', template_model='template_model') + template_id='template_id', template_alias='template_alias', + template_model='template_model') self.assertEqual(sorted(msg), sorted(Message._fields)) self.assertNotRaises(TypeError, Message.load_message, msg) self.assertNotRaises(MessageError, Message.load_message, msg,