Updated for Sendy 4.0.5. Removes Python 2 support.
5 files changed, 64 insertions(+), 88 deletions(-)

M README.rst
M sendy/__init__.py
M sendy/api.py
M sendy/binder.py
M sendy/exceptions.py
M README.rst +4 -2
@@ 5,17 5,19 @@ 
 sendypy |nlshield|
 ==============================
 :Info: Simple module to interface with the Sendy API (https://sendy.co/api)
-:Version: 0.2
+:Version: 4.0.5
 :Author: Peter Sanchez (http://www.petersanchez.com) - (http://www.netlandish.com)
 
 Dependencies
 ============
 
-* It was written for Python 2.7+ and Python 3.6+
+* It was written for Python 3.6+
 * Depends on the requests module
 
 **NOTE:**
 
+* **Version 4.0.5** we're changing the version to match the version of Sendy that this is most recently updated for. When Sendy introduces changes that require a lib update, we will update the version to match the Sendy version.
+
 * **Version 0.2** now requires ``api_key`` varaible for all API calls. This is a required change to the functionality of Sendy as of version 4.0.4. This is a potentially breaking code change. Please update your code to always provide the API key before upgrading to sendypy v0.2.
 
 * **Version 0.1.3b** adds the new "title" variable to the ``create_campaign`` API call. It effects the default ordering of variables in the call. If you're upgrading from a previous version please ensure that you account for the new ordering of variables. This new variable was added in Sendy v2.1.2.6

          
M sendy/__init__.py +1 -3
@@ 1,8 1,6 @@ 
-from __future__ import unicode_literals
-
 name = 'sendypy'
 
-VERSION = (0, 2, 0, 'final', 0)
+VERSION = (4, 0, 5, 'final', 0)
 
 
 def get_version():

          
M sendy/api.py +42 -50
@@ 1,5 1,3 @@ 
-from __future__ import unicode_literals
-
 import logging
 
 from .binder import bind_api

          
@@ 13,76 11,70 @@ class SendyAPI:
 
     def __init__(self, host, api_key, debug=False):
         self.host = host
-        if self.host[-1] != '/':
-            self.host = '{0}/'.format(self.host)
+        if self.host[-1] != "/":
+            self.host = "{0}/".format(self.host)
         self.api_key = api_key
         self.debug = debug
 
     subscribe = bind_api(
-        path='subscribe',
-        allowed_param=[
-            'list',
-            'email',
-            'name',
-        ],
-        extra_param={'boolean': 'true'},
-        success_message='1',
-        method='POST',
+        path="subscribe",
+        allowed_param=["list", "email", "name",],
+        extra_param={"boolean": "true"},
+        success_message="1",
+        method="POST",
     )
 
     unsubscribe = bind_api(
-        path='unsubscribe',
-        allowed_param=['list', 'email'],
-        extra_param={'boolean': 'true'},
-        success_message='1',
-        method='POST',
+        path="unsubscribe",
+        allowed_param=["list", "email"],
+        extra_param={"boolean": "true"},
+        success_message="1",
+        method="POST",
     )
 
     delete = bind_api(
-        path='api/subscribers/delete.php',
-        allowed_param=['list_id', 'email'],
-        success_message='1',
-        method='POST',
+        path="api/subscribers/delete.php",
+        allowed_param=["list_id", "email"],
+        success_message="1",
+        method="POST",
     )
 
     subscription_status = bind_api(
-        path='api/subscribers/subscription-status.php',
-        allowed_param=['list_id', 'email'],
+        path="api/subscribers/subscription-status.php",
+        allowed_param=["list_id", "email"],
         success_message=[
-            'Subscribed',
-            'Unsubscribed',
-            'Unconfirmed',
-            'Bounced',
-            'Soft bounced',
-            'Complained',
+            "Subscribed",
+            "Unsubscribed",
+            "Unconfirmed",
+            "Bounced",
+            "Soft bounced",
+            "Complained",
         ],
-        method='POST',
+        method="POST",
     )
 
     subscriber_count = bind_api(
-        path='api/subscribers/active-subscriber-count.php',
-        allowed_param=['list_id'],
+        path="api/subscribers/active-subscriber-count.php",
+        allowed_param=["list_id"],
         success_message=int,
-        method='POST',
+        method="POST",
     )
 
     create_campaign = bind_api(
-        path='api/campaigns/create.php',
+        path="api/campaigns/create.php",
         allowed_param=[
-            'from_name',
-            'from_email',
-            'reply_to',
-            'title',
-            'subject',
-            'plain_text',
-            'html_text',
-            'list_ids',
-            'brand_id',
-            'query_string',
-            'send_campaign',
+            "from_name",
+            "from_email",
+            "reply_to",
+            "title",
+            "subject",
+            "plain_text",
+            "html_text",
+            "list_ids",
+            "brand_id",
+            "query_string",
+            "send_campaign",
         ],
-        success_message=[
-            'Campaign created', 'Campaign created and now sending'
-        ],
-        method='POST',
+        success_message=["Campaign created", "Campaign created and now sending"],
+        method="POST",
     )

          
M sendy/binder.py +17 -30
@@ 1,46 1,33 @@ 
-from __future__ import unicode_literals
-
 import logging
+from urllib.parse import urljoin
 
 import requests
 
-try:
-    from urlparse import urljoin
-except ImportError:
-    from urllib.parse import urljoin
-
 from .exceptions import SendyError
 
-
 log = logging.getLogger(__name__)
 
 
 def convert_to_utf8(value):
-    try:
-        unicode
-    except NameError:
-        return value
-    if isinstance(value, str):
-        value = unicode(value)
-    if isinstance(value, unicode):
-        return value.decode('utf-8')
+    if isinstance(value, bytes):
+        value = value.decode("utf-8")
     return value
 
 
 def bind_api(**config):
     class APIMethod:
-        path = config['path']
-        allowed_param = config.get('allowed_param', [])
-        method = config.get('method', 'GET')
-        success_status_code = config.get('success_status_code', 200)
-        success_message = config.get('success_message', None)
-        extra_param = config.get('extra_param', None)
-        fail_silently = config.get('fail_silently', True)
+        path = config["path"]
+        allowed_param = config.get("allowed_param", [])
+        method = config.get("method", "GET")
+        success_status_code = config.get("success_status_code", 200)
+        success_message = config.get("success_message", None)
+        extra_param = config.get("extra_param", None)
+        fail_silently = config.get("fail_silently", True)
 
         def __init__(self, api, args, kwargs):
             self.api = api
-            self.headers = kwargs.pop('headers', {})
-            self.post_data = kwargs.pop('post_data', None)
+            self.headers = kwargs.pop("headers", {})
+            self.post_data = kwargs.pop("post_data", None)
             self.build_parameters(args, kwargs)
             self.path = self.path.format(**self.parameters)  # Sub any URL vars
             self.host = api.host

          
@@ 57,14 44,14 @@ def bind_api(**config):
                 try:
                     self.parameters[self.allowed_param[idx]] = convert_to_utf8(arg)
                 except IndexError:
-                    raise ValueError('Too many parameters supplied!')
+                    raise ValueError("Too many parameters supplied!")
 
             for k, arg in kwargs.items():
                 if arg is None:
                     continue
                 if k in self.parameters:
                     raise ValueError(
-                        'Multiple values for parameter {0} supplied!'.format(k)
+                        "Multiple values for parameter {0} supplied!".format(k)
                     )
 
                 self.parameters[k] = convert_to_utf8(arg)

          
@@ 72,7 59,7 @@ def bind_api(**config):
             if self.extra_param is not None:
                 self.parameters.update(self.extra_param)
 
-            self.parameters['api_key'] = self.api.api_key
+            self.parameters["api_key"] = self.api.api_key
 
         def execute(self):
             # Build the request URL

          
@@ 80,7 67,7 @@ def bind_api(**config):
 
             if self.api.debug:
                 log.info(
-                    '* Sending {0}: {1}\nHeaders: {2}\nData:{3}'.format(
+                    "* Sending {0}: {1}\nHeaders: {2}\nData:{3}".format(
                         self.method, url, self.headers, self.post_data
                     )
                 )

          
@@ 99,7 86,7 @@ def bind_api(**config):
             success = data in self.success_message
             if not success and not self.fail_silently:
                 raise SendyError(data)
-            return data
+            return convert_to_utf8(data)
 
     def _call(api, *args, **kwargs):
         method = APIMethod(api, args, kwargs)

          
M sendy/exceptions.py +0 -3
@@ 1,5 1,2 @@ 
-from __future__ import unicode_literals
-
-
 class SendyError(Exception):
     pass