Skip to content

Commit

Permalink
Merge pull request #27 from izquierdo/python3
Browse files Browse the repository at this point in the history
Support Python 3
  • Loading branch information
asadighi authored Jul 6, 2016
2 parents 2370767 + 4a81ea7 commit 6113b17
Show file tree
Hide file tree
Showing 25 changed files with 125 additions and 113 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ language: python
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
script:
- coverage run --source=createsend nosetests.py
- coverage report
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# createsend

A Python library which implements the complete functionality of the [Campaign Monitor API](http://www.campaignmonitor.com/api/). Requires Python 2.5, 2.6, or 2.7.
A Python library which implements the complete functionality of the [Campaign Monitor API](http://www.campaignmonitor.com/api/). Requires Python 2.6, 2.7, 3.3, 3.4, or 3.5.

## Installation

Expand Down
23 changes: 11 additions & 12 deletions createsend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from createsend import __version__
from createsend import *
from client import Client
from template import Template
from list import List
from segment import Segment
from subscriber import Subscriber
from campaign import Campaign
from person import Person
from administrator import Administrator
from transactional import Transactional
import utils
from .createsend import *
from .client import Client
from .template import Template
from .list import List
from .segment import Segment
from .subscriber import Subscriber
from .campaign import Campaign
from .person import Person
from .administrator import Administrator
from .transactional import Transactional
from . import utils
4 changes: 2 additions & 2 deletions createsend/administrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase, BadRequest
from utils import json_to_py
from .createsend import CreateSendBase, BadRequest
from .utils import json_to_py

class Administrator(CreateSendBase):
"""Represents an administrator and associated functionality."""
Expand Down
4 changes: 2 additions & 2 deletions createsend/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase
from utils import json_to_py
from .createsend import CreateSendBase
from .utils import json_to_py

class Campaign(CreateSendBase):
"""Represents a campaign and provides associated functionality."""
Expand Down
28 changes: 14 additions & 14 deletions createsend/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase
from utils import json_to_py
from .createsend import CreateSendBase
from .utils import json_to_py

class Client(CreateSendBase):
"""Represents a client and associated functionality."""
Expand Down Expand Up @@ -145,20 +145,20 @@ def transfer_credits(self, credits, can_use_my_credits_when_they_run_out):
return json_to_py(response)

def people(self):
"""gets people associated with the client"""
response = self._get(self.uri_for('people'))
return json_to_py(response)
"""gets people associated with the client"""
response = self._get(self.uri_for('people'))
return json_to_py(response)

def get_primary_contact(self):
"""retrieves the primary contact for this client"""
response = self._get(self.uri_for('primarycontact'))
return json_to_py(response)
"""retrieves the primary contact for this client"""
response = self._get(self.uri_for('primarycontact'))
return json_to_py(response)

def set_primary_contact(self, email):
"""assigns the primary contact for this client"""
params = { "email": email }
response = self._put(self.uri_for('primarycontact'), params = params)
return json_to_py(response)
"""assigns the primary contact for this client"""
params = { "email": email }
response = self._put(self.uri_for('primarycontact'), params = params)
return json_to_py(response)

def delete(self):
"""Deletes this client."""
Expand Down
38 changes: 24 additions & 14 deletions createsend/createsend.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import sys
import platform
import urllib
import urllib2
import base64
import gzip
import os
from StringIO import StringIO
from urlparse import urlparse
from six import BytesIO
from six.moves.urllib.parse import parse_qs, urlencode, urlparse
try:
import json
except ImportError:
import simplejson as json
from utils import VerifiedHTTPSConnection, json_to_py, get_faker
from .utils import VerifiedHTTPSConnection, json_to_py, get_faker

__version_info__ = ('4', '1', '1')
__version__ = '.'.join(__version_info__)
Expand Down Expand Up @@ -54,7 +52,7 @@ def authorize_url(self, client_id, redirect_uri, scope, state=None):
]
if state:
params.append(('state', state))
return "%s?%s" % (CreateSend.oauth_uri, urllib.urlencode(params))
return "%s?%s" % (CreateSend.oauth_uri, urlencode(params))

def exchange_token(self, client_id, client_secret, redirect_uri, code):
"""Exchange a provided OAuth code for an OAuth access token, 'expires in'
Expand All @@ -66,7 +64,7 @@ def exchange_token(self, client_id, client_secret, redirect_uri, code):
('redirect_uri', redirect_uri),
('code', code),
]
response = self._post('', urllib.urlencode(params),
response = self._post('', urlencode(params),
CreateSend.oauth_token_uri, "application/x-www-form-urlencoded")
access_token, expires_in, refresh_token = None, None, None
r = json_to_py(response)
Expand Down Expand Up @@ -102,7 +100,7 @@ def refresh_token(self):
('grant_type', 'refresh_token'),
('refresh_token', refresh_token)
]
response = self._post('', urllib.urlencode(params),
response = self._post('', urlencode(params),
CreateSend.oauth_token_uri, "application/x-www-form-urlencoded")
new_access_token, new_expires_in, new_refresh_token = None, None, None
r = json_to_py(response)
Expand Down Expand Up @@ -130,10 +128,10 @@ def make_request(self, method, path, params={}, body="", username=None,
the default basic authentication mechanism using the API key be
overridden (e.g. when using the apikey route with username and password)."""
if username and password:
headers['Authorization'] = "Basic %s" % base64.b64encode("%s:%s" % (username, password))
headers['Authorization'] = "Basic %s" % base64.b64encode(("%s:%s" % (username, password)).encode()).decode()
elif self.auth_details:
if 'api_key' in self.auth_details and self.auth_details['api_key']:
headers['Authorization'] = "Basic %s" % base64.b64encode("%s:x" % self.auth_details['api_key'])
headers['Authorization'] = "Basic %s" % base64.b64encode(("%s:x" % self.auth_details['api_key']).encode()).decode()
elif 'access_token' in self.auth_details and self.auth_details['access_token']:
headers['Authorization'] = "Bearer %s" % self.auth_details['access_token']
self.headers = headers
Expand All @@ -144,12 +142,24 @@ def make_request(self, method, path, params={}, body="", username=None,
# Check that the actual url which would be requested matches self.faker.url.
actual_url = "https://%s%s" % (parsed_base_uri.netloc, self.build_url(parsed_base_uri, path, params))
self.faker.actual_url = actual_url
if self.faker.url != actual_url:
def same_urls(url_a, url_b):
a = urlparse(url_a)
b = urlparse(url_b)
return (a.scheme == b.scheme and
a.netloc == b.netloc and
a.path == b.path and
a.params == b.params and
parse_qs(a.query) == parse_qs(b.query) and
a.fragment == b.fragment
)
if not same_urls(self.faker.url, actual_url):
raise Exception("Faker's expected URL (%s) doesn't match actual URL (%s)" % (self.faker.url, actual_url))

self.faker.actual_body = body
def same_bodies(body_a, body_b):
return json.loads(body_a) == json.loads(body_b)
if self.faker.body is not None:
if self.faker.body != body:
if not same_bodies(self.faker.body, body):
raise Exception("Faker's expected body (%s) doesn't match actual body (%s)" % (self.faker.body, body))

data = self.faker.open() if self.faker else ''
Expand All @@ -160,7 +170,7 @@ def make_request(self, method, path, params={}, body="", username=None,
c.request(method, self.build_url(parsed_base_uri, path, params), body, headers)
response = c.getresponse()
if response.getheader('content-encoding', '') == 'gzip':
data = gzip.GzipFile(fileobj=StringIO(response.read())).read()
data = gzip.GzipFile(fileobj=BytesIO(response.read())).read()
else:
data = response.read()
c.close()
Expand All @@ -169,7 +179,7 @@ def make_request(self, method, path, params={}, body="", username=None,
def build_url(self, parsed_base_uri, path, params):
url = parsed_base_uri.path + path
if params and len(params) > 0:
url = (url + "?%s" % urllib.urlencode(params))
url = (url + "?%s" % urlencode(params))
return url

def handle_response(self, status, data):
Expand Down
12 changes: 6 additions & 6 deletions createsend/list.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import urllib
from six.moves.urllib.parse import quote
try:
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase
from utils import json_to_py
from .createsend import CreateSendBase
from .utils import json_to_py

class List(CreateSendBase):
"""Represents a subscriber list and associated functionality."""
Expand Down Expand Up @@ -44,7 +44,7 @@ def create_custom_field(self, field_name, data_type, options=[],
def update_custom_field(self, custom_field_key, field_name,
visible_in_preference_center):
"""Updates a custom field belonging to this list."""
custom_field_key = urllib.quote(custom_field_key, '')
custom_field_key = quote(custom_field_key, '')
body = {
"FieldName": field_name,
"VisibleInPreferenceCenter": visible_in_preference_center }
Expand All @@ -53,14 +53,14 @@ def update_custom_field(self, custom_field_key, field_name,

def delete_custom_field(self, custom_field_key):
"""Deletes a custom field associated with this list."""
custom_field_key = urllib.quote(custom_field_key, '')
custom_field_key = quote(custom_field_key, '')
response = self._delete("/lists/%s/customfields/%s.json" %
(self.list_id, custom_field_key))

def update_custom_field_options(self, custom_field_key, new_options,
keep_existing_options):
"""Updates the options of a multi-optioned custom field on this list."""
custom_field_key = urllib.quote(custom_field_key, '')
custom_field_key = quote(custom_field_key, '')
body = {
"Options": new_options,
"KeepExistingOptions": keep_existing_options }
Expand Down
4 changes: 2 additions & 2 deletions createsend/person.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase, BadRequest
from utils import json_to_py
from .createsend import CreateSendBase, BadRequest
from .utils import json_to_py

class Person(CreateSendBase):
"""Represents a person and associated functionality."""
Expand Down
4 changes: 2 additions & 2 deletions createsend/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase
from utils import json_to_py
from .createsend import CreateSendBase
from .utils import json_to_py

class Segment(CreateSendBase):
"""Represents a subscriber list segment and associated functionality."""
Expand Down
6 changes: 3 additions & 3 deletions createsend/subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase, BadRequest
from utils import json_to_py
from .createsend import CreateSendBase, BadRequest
from .utils import json_to_py

class Subscriber(CreateSendBase):
"""Represents a subscriber and associated functionality."""
Expand Down Expand Up @@ -54,7 +54,7 @@ def import_subscribers(self, list_id, subscribers, resubscribe, queue_subscripti
"RestartSubscriptionBasedAutoresponders": restart_subscription_based_autoresponders }
try:
response = self._post("/subscribers/%s/import.json" % list_id, json.dumps(body))
except BadRequest, br:
except BadRequest as br:
# Subscriber import will throw BadRequest if some subscribers are not imported
# successfully. If this occurs, we want to return the ResultData property of
# the BadRequest exception (which is of the same "form" as the response we'd
Expand Down
4 changes: 2 additions & 2 deletions createsend/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase
from utils import json_to_py
from .createsend import CreateSendBase
from .utils import json_to_py

class Template(CreateSendBase):
"""Represents an email template and associated functionality."""
Expand Down
4 changes: 2 additions & 2 deletions createsend/transactional.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import json
except ImportError:
import simplejson as json
from createsend import CreateSendBase
from utils import json_to_py
from .createsend import CreateSendBase
from .utils import json_to_py

class Transactional(CreateSendBase):
"""Represents transactional functionality."""
Expand Down
8 changes: 4 additions & 4 deletions createsend/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import re
import httplib
from six.moves.http_client import HTTPSConnection
import socket
import ssl
try:
Expand Down Expand Up @@ -69,7 +69,7 @@ def match_hostname(cert, hostname):
raise CertificateError("no appropriate commonName or "
"subjectAltName fields were found")

class VerifiedHTTPSConnection(httplib.HTTPSConnection):
class VerifiedHTTPSConnection(HTTPSConnection):
"""
A connection that includes SSL certificate verification.
"""
Expand Down Expand Up @@ -107,7 +107,7 @@ def connect(self):
raise

def json_to_py(j):
o = json.loads(j)
o = json.loads(j.decode())
if isinstance(o, dict):
return dict_to_object(o)
else:
Expand Down Expand Up @@ -140,7 +140,7 @@ def __init__(self, expected_url, filename, status, body = None):

def open(self):
if self.filename:
return open("%s/../test/fixtures/%s" % (os.path.dirname(__file__), self.filename)).read()
return open("%s/../test/fixtures/%s" % (os.path.dirname(__file__), self.filename), mode='rb').read()
else:
return ''

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
nose==1.2.1
coverage
coveralls
six
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from distutils.core import setup

from createsend import __version__
from createsend.createsend import __version__

setup(name = "createsend",
version = __version__,
Expand Down
Loading

0 comments on commit 6113b17

Please sign in to comment.