Skip to content
This repository has been archived by the owner on Aug 29, 2019. It is now read-only.

Commit

Permalink
bumped changelog, refactored tests and methods, fixed bug in find met…
Browse files Browse the repository at this point in the history
…hods
  • Loading branch information
Edward F. Long, Jr committed Aug 25, 2011
1 parent 309d9f1 commit 415052f
Show file tree
Hide file tree
Showing 12 changed files with 84 additions and 56 deletions.
Empty file added CHANGELOG.rst
Empty file.
36 changes: 33 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ Or can be installed using easy_install::

$ easy_install aweber_api



Usage
=====

To connect the AWeber API Python Libray, you simply include the main class,
AWeberAPI in your application, then create an instace of it with your
application's consumer key and secret::
application's consumer key and secret.::

from aweber_api import AWeberAPI
aweber = AWeberAPI(consumer_key, consumer_secret)
Expand All @@ -32,6 +30,38 @@ application's consumer key and secret::
for list in account.lists:
print list.name

Handling Errors
+++++++++++++++

Sometimes errors happen and your application should handle them appropriately.
Whenever an API error occurs an AWeberAPIException will be raised with a
detailed error message and documentation link to explain whats wrong.

You should wrap any calls to the API in a try/except block.

Common Errors:
* Resource not found (404 error)
* Your application has been rate limited (403 error)
* Bad request (400 error)
* API Temporarily unavailable (503 error)

Refer to https://labs.aweber.com/docs/troubleshooting for the complete list::

from aweber_api import AWeberAPI, APIException
aweber = AWeberAPI(consumer_key, consumer_secret)
account = aweber.get_account(access_token, token_secret)


try:
invalid_resource = account.load_from_url('/idontexist')
except APIException, exc:
print '404! {0}'.format(exc)

try:
print len(account.lists)
except APIException, exc:
print 'hmm, something unexpected happened!: {0}'.format(exc)


Getting request tokens / access tokens
++++++++++++++++++++++++++++++++++++++
Expand Down
5 changes: 3 additions & 2 deletions aweber_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from urlparse import parse_qs

from aweber_api.base import (AWeberBase, API_BASE, ACCESS_TOKEN_URL,
REQUEST_TOKEN_URL, AUTHORIZE_URL)
REQUEST_TOKEN_URL, AUTHORIZE_URL, APIException)
from aweber_api.collection import AWeberCollection
from aweber_api.entry import AWeberEntry
from aweber_api.oauth import OAuthAdapter
from aweber_api.response import AWeberResponse


class AWeberAPI(AWeberBase):
""" Base class for connecting to the AWeberAPI. Created with a consumer key
and secret, then used to either generate tokens for authorizing a user, or
Expand Down Expand Up @@ -83,6 +84,7 @@ def get_account(self, access_token=False, token_secret=False):
accounts = self._read_response(url, response)
return accounts[0]


class AWeberUser(object):
"""
Simple data storage object representing the user in the OAuth model. Has
Expand All @@ -96,4 +98,3 @@ class AWeberUser(object):

def get_highest_priority_token(self):
return self.access_token or self.request_token

5 changes: 5 additions & 0 deletions aweber_api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
REQUEST_TOKEN_URL = 'https://auth.aweber.com/1.0/oauth/request_token'
AUTHORIZE_URL = 'https://auth.aweber.com/1.0/oauth/authorize'


class APIException(Exception):
"""APIExceptions."""


class AWeberBase(object):
"""
Provides functionality shared accross all AWeber objects
Expand Down
35 changes: 18 additions & 17 deletions aweber_api/collection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from math import floor
from urlparse import parse_qs
from urllib import urlencode
from aweber_api.response import AWeberResponse
from aweber_api.base import API_BASE

import aweber_api
from aweber_api.response import AWeberResponse


class AWeberCollection(AWeberResponse):
Expand Down Expand Up @@ -45,7 +45,11 @@ def _load_page_for_offset(self, offset):
self._key_entries(response)

def _get_page_params(self, offset):
next_link = self._data.get('next_collection_link', '')
next_link = self._data.get('next_collection_link', None)
if next_link is None:
"""no more parameters in page!"""
raise StopIteration

url, query = next_link.split('?')
query_parts = parse_qs(query)
self.page_size = int(query_parts['ws.size'][0])
Expand All @@ -62,20 +66,16 @@ def create(self, **kwargs):

resource_url = response['location']
data = self.adapter.request('GET', resource_url)
return aweber_api.entry.AWeberEntry(resource_url, data, self.adapter)
return aweber_api.AWeberEntry(resource_url, data, self.adapter)

def find(self, **kwargs):
params = {'ws.op': 'find'}
params.update(kwargs)
query_string = urlencode(params)
url = '{0.url}?{1}'.format(self, query_string)
data = self.adapter.request('GET', url)
try:
collection = AWeberCollection(url, data, self.adapter)
except TypeError:
return False

# collections return total_size_link
collection = AWeberCollection(url, data, self.adapter)
collection._data['total_size'] = self._get_total_size(url)
return collection

Expand All @@ -84,29 +84,30 @@ def _get_total_size(self, uri, **kwargs):
total_size_uri = '{0}&ws.show=total_size'.format(uri)
return self.adapter.request('GET', total_size_uri)

# This method gets a collection's parent entry
# Or returns None if no parent entry
def get_parent_entry(self):
from aweber_api.entry import AWeberEntry
"""Return a collection's parent entry or None."""
url_parts = self.url.split('/')

#If top of tree - no parent entry
if len(url_parts) <= 3:
return None
size = len(url_parts)

#Remove collection id and slash from end of url
url = self.url[:-len(url_parts[size-1])-1]
data = self.adapter.request('GET', url)
try:
entry = AWeberEntry(url, data, self.adapter)
entry = aweber_api.AWeberEntry(url, data, self.adapter)
except TypeError:
return False
return None

return entry

def _create_entry(self, offset):
from aweber_api.entry import AWeberEntry
data = self._entry_data[offset]
url = data['self_link'].replace(API_BASE, '')
self._entries[offset] = AWeberEntry(url, data, self.adapter)
url = data['self_link'].replace(aweber_api.API_BASE, '')
self._entries[offset] = aweber_api.AWeberEntry(url, data,
self.adapter)

def __len__(self):
return self.total_size
Expand Down
33 changes: 10 additions & 23 deletions aweber_api/entry.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import aweber_api
from aweber_api.response import AWeberResponse
from aweber_api.data_dict import DataDict
from aweber_api import AWeberCollection

from urllib import urlencode


Expand Down Expand Up @@ -39,10 +40,8 @@ def delete(self):
https://labs.aweber.com/docs/reference/1.0 for more
details on which entry resources may be deleted.
"""
status = self.adapter.request('DELETE', self.url, response='status')
if str(status)[:2] == '20':
return True
return False
self.adapter.request('DELETE', self.url, response='status')
return True

def move(self, list_):
"""Invoke the API method to MOVE an entry resource to a
Expand All @@ -58,20 +57,16 @@ def move(self, list_):
'list_link': list_.self_link}
response = self.adapter.request('POST', self.url, params,
response='headers')
if response['status'] != '201':
return False

new_resource = response['location']
self._diff = {}
self._data = self.adapter.request('GET', new_resource)
return True

def save(self):
response = self.adapter.request('PATCH', self.url, self._diff,
response='status')
self.adapter.request('PATCH', self.url, self._diff, response='status')
self._diff = {}
if str(response)[:2] == '20':
return True
return False
return True

def get_activity(self):
"""Invoke the API method to return all Subscriber activity.
Expand All @@ -86,12 +81,8 @@ def get_activity(self):
query_string = urlencode(params)
url = '{0.url}?{1}'.format(self, query_string)
data = self.adapter.request('GET', url)
try:
collection = AWeberCollection(url, data, self.adapter)
except TypeError:
return False

# collections return total_size_link
collection = aweber_api.AWeberCollection(url, data, self.adapter)
collection._data['total_size'] = self._get_total_size(url)
return collection

Expand All @@ -109,13 +100,9 @@ def findSubscribers(self, **kwargs):
params.update(kwargs)
query_string = urlencode(params)
url = '{0.url}?{1}'.format(self, query_string)
data = self.adapter.request('GET', url)
try:
collection = AWeberCollection(url, data, self.adapter)
except TypeError:
return False

# collections return total_size_link
data = self.adapter.request('GET', url)
collection = aweber_api.AWeberCollection(url, data, self.adapter)
collection._data['total_size'] = self._get_total_size(url)
return collection

Expand Down
6 changes: 5 additions & 1 deletion aweber_api/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import json
from urllib import urlencode

import aweber_api


class OAuthAdapter(object):

def __init__(self, key, secret, base):
Expand Down Expand Up @@ -53,7 +56,8 @@ def request(self, method, url, data={}, response='body'):
error = content.get('error', {})
error_type = error.get('type')
error_msg = error.get('message')
raise Exception('{0}: {1}'.format(error_type, error_msg))
raise aweber_api.base.APIException(
'{0}: {1}'.format(error_type, error_msg))

if response == 'body' and isinstance(content, str):
return self._parse(content)
Expand Down
1 change: 1 addition & 0 deletions aweber_api/response.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from aweber_api import AWeberBase


class AWeberResponse(AWeberBase):

def __init__(self, url, data, adapter):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='aweber_api',
version='1.0.4',
version='1.1.0',
packages=find_packages(exclude=['tests']),
url='https://github.com/aweber/AWeber-API-Python-Library',
install_requires = ['oauth2 >= 1.2'],
Expand Down
2 changes: 1 addition & 1 deletion tests/mock_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
'location': '/accounts/1/lists/505454/subscribers/3'}, None),
},
'PATCH' : {
'/accounts/1/lists/303449/subscribers/1': ({}, None),
'/accounts/1/lists/303449/subscribers/1': ({'status': '209'}, None),
'/accounts/1/lists/303449/subscribers/2': ({'status': '400'}, 'error'),
},
'DELETE' : {
Expand Down
6 changes: 3 additions & 3 deletions tests/test_aweber_collection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from unittest import TestCase
from aweber_api import AWeberAPI, AWeberCollection, AWeberEntry
from aweber_api.base import API_BASE
from aweber_api.base import API_BASE, APIException
from mock_adapter import MockAdapter


Expand Down Expand Up @@ -56,7 +56,7 @@ def test_should_support_find_method(self):
def test_find_should_handle_errors(self):
base_url = '/accounts/1/lists/303449/subscribers'
subscriber_collection = self.aweber.load_from_url(base_url)
self.assertRaises(Exception, subscriber_collection.find, name='joe')
self.assertRaises(APIException, subscriber_collection.find, name='joe')

def test_should_create_entries_with_correct_url(self):
base_url = '/accounts/1'
Expand All @@ -76,7 +76,7 @@ def setUp(self):
self.aweber.adapter.requests = []

def test_should_raise_exception(self):
self.assertRaises(Exception, self.cf.create, name='Duplicate Name')
self.assertRaises(APIException, self.cf.create, name='Duplicate Name')


class TestCreatingCustomFields(TestCase):
Expand Down
9 changes: 4 additions & 5 deletions tests/test_aweber_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from urllib import urlencode

from aweber_api import AWeberAPI, AWeberCollection, AWeberEntry
from aweber_api.base import APIException
from mock_adapter import MockAdapter


Expand All @@ -29,7 +30,7 @@ def test_should_have_child_collections(self):

def test_findSubscribers_should_handle_errors(self):
account = self.aweber.load_from_url('/accounts/1')
self.assertRaises(Exception, account.findSubscribers, name='bob')
self.assertRaises(APIException, account.findSubscribers, name='bob')


class AccountTestCase(TestCase):
Expand Down Expand Up @@ -227,7 +228,7 @@ def setUp(self):
self.subscriber.custom_fields['New Custom Field'] = 'Cookies'

def test_save_failed(self):
self.assertRaises(Exception, self.subscriber.save)
self.assertRaises(APIException, self.subscriber.save)


class TestDeletingSubscriberData(SubscriberTestCase):
Expand Down Expand Up @@ -257,7 +258,7 @@ def setUp(self):
self.subscriber = self.aweber.load_from_url(sub_url)

def test_should_raise_exception_when_failing(self):
self.assertRaises(Exception, self.subscriber.delete)
self.assertRaises(APIException, self.subscriber.delete)


class TestGettingParentEntry(TestCase):
Expand All @@ -267,8 +268,6 @@ def setUp(self):
self.aweber.adapter = MockAdapter()
self.list = self.aweber.load_from_url('/accounts/1/lists/303449')
self.account = self.aweber.load_from_url('/accounts/1')
#print self.account._data
#1/0
self.custom_field = self.aweber.load_from_url('/accounts/1/lists/303449/custom_fields/1')

def test_should_be_able_get_parent_entry(self):
Expand Down

0 comments on commit 415052f

Please sign in to comment.