Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 95af393c5cc7df10a00ac7fa8c95cdcfcab73919 @stevenwei stevenwei committed Apr 20, 2010
Showing with 307 additions and 0 deletions.
  1. +143 −0 chargify.py
  2. +164 −0 test_chargify.py
143 chargify.py
@@ -0,0 +1,143 @@
+import simplejson
+import urllib
+import urllib2
+import base64
+
+# Define all exceptions
+# Corresponds to HTTP response codes specified here:
+# http://support.chargify.com/faqs/api/api-user-guide
+class ChargifyError(Exception):pass
+class ChargifyConnectionError(ChargifyError):pass
+class ChargifyUnauthorizedError(ChargifyError):pass
+class ChargifyForbiddenError(ChargifyError):pass
+class ChargifyNotFoundError(ChargifyError):pass
+class ChargifyUnprocessableEntityError(ChargifyError):pass
+class ChargifyServerError(ChargifyError):pass
+
+# Maps certain function names to HTTP verbs
+VERBS = {
+ 'create':'POST',
+ 'read':'GET',
+ 'update':'PUT',
+ 'delete':'DELETE'
+}
+
+# A list of identifiers that should be extracted and placed into the url string if they are
+# passed into the kwargs.
+IDENTIFIERS = {
+ 'customer_id':'customers',
+ 'product_id':'products',
+ 'subscription_id':'subscriptions',
+ 'component_id':'components',
+ 'handle':'handle'
+}
+
+class Chargify(object):
+ api_key = ''
+ sub_domain = ''
+ path = []
+ domain = 'https://%s.chargify.com/'
+
+ def __init__(self, api_key, sub_domain, path=None):
+ """
+ :param api_key: The API key for your Chargify account.
+ :param sub_domain: The sub domain of your Chargify account.
+ :param path: The current path constructed for this request.
+ """
+ self.api_key = api_key
+ self.sub_domain = sub_domain
+ self.path = path or []
+
+ def __getattr__(self, attr):
+ """
+ Uses attribute chaining to help construct the url path of the request.
+ """
+ try:
+ return object.__getattr__(self, attr)
+ except AttributeError:
+ return Chargify(self.api_key, self.sub_domain, self.path + [attr])
+
+ def construct_request(self, **kwargs):
+ """
+ :param kwargs: The arguments passed into the request. Valid values are:
+ 'customer_id', 'product_id', 'subscription_id', 'component_id', 'handle' will be extracted
+ and placed into the url. 'data' will be serialized into a JSON string and POSTed with
+ the request.
+ """
+ path = self.path[:]
+
+ # Find the HTTP method if we were called with create(), update(), read(), or delete()
+ if path[-1] in VERBS.keys():
+ action = path.pop()
+ method = VERBS[action]
+ else:
+ method = 'GET'
+
+ # Extract certain kwargs and place them in the url instead
+ for identifier, name in IDENTIFIERS.items():
+ value = kwargs.pop(identifier, None)
+ if value:
+ path.insert(path.index(name)+1, str(value))
+
+ # Convert the data to a JSON string
+ data = kwargs.pop('data', None)
+ if data:
+ data = simplejson.dumps(data)
+
+ # Build query string
+ if method == 'GET' and kwargs:
+ args = "?%s" % (urllib.urlencode(kwargs.items()))
+ else:
+ args = ''
+
+ # Build url
+ url = self.domain % self.sub_domain
+ url = url + '/'.join(path) + '.json' + args
+
+ return url, method, data
+
+ def make_request(self, url, method, data):
+ """
+ Actually responsible for making the HTTP request.
+ :param url: The URL to load.
+ :param method: The HTTP method to use.
+ :param data: Any POST data that should be included with the request.
+ """
+ opener = urllib2.build_opener(urllib2.HTTPHandler)
+ request = urllib2.Request(url=url, data=data)
+
+ # Build header
+ request.get_method = lambda: method
+ request.add_header('Authorization', 'Basic %s' % base64.encodestring('%s:%s' % (self.api_key, 'x'))[:-1])
+ request.add_header('User-Agent', 'Chargify Python Client')
+ request.add_header('Accept', 'application/json')
+ request.add_header('Content-Type', 'application/json')
+
+ # Make request and trap for HTTP errors
+ try:
+ response = opener.open(request)
+ result = response.read()
+ except urllib2.HTTPError, e:
+ if e.code == 401:
+ raise ChargifyUnauthorizedError(e)
+ elif e.code == 403:
+ raise ChargifyForbiddenError(e)
+ elif e.code == 404:
+ raise ChargifyNotFoundError(e)
+ elif e.code == 422:
+ raise ChargifyUnprocessableEntityError(e)
+ elif e.code == 500:
+ raise ChargifyServerError(e)
+ else:
+ raise ChargifyError(e)
+ except urllib2.URLError, e:
+ raise ChargifyConnectionError(e)
+
+ # Parse and return JSON Result
+ return simplejson.loads(result)
+
+ def __call__(self, **kwargs):
+ url, method, data = self.construct_request(**kwargs)
+ return self.make_request(url, method, data)
+
+
164 test_chargify.py
@@ -0,0 +1,164 @@
+import unittest
+from chargify import Chargify, ChargifyError
+
+class ChargifyTestCase(unittest.TestCase):
+
+ def assertResult(self, result, expected_url, expected_method, expected_data):
+ """
+ A little helper method to help verify that the correct URL, HTTP method, and POST data are
+ being constructed from the Chargify API.
+ """
+ url, method, data = result
+ print url, method, data
+ self.assertEqual(url,expected_url)
+ self.assertEqual(method,expected_method)
+ self.assertEqual(data,expected_data)
+
+class TestCustomers(ChargifyTestCase):
+
+ def test_construct_request(self):
+ chargify = Chargify('api_key','subdomain')
+
+ # List
+ result = chargify.customers.construct_request()
+ self.assertResult(result,'https://subdomain.chargify.com/customers.json','GET',None)
+
+ # Read/show (via chargify id)
+ result = chargify.customers.construct_request(customer_id=123)
+ self.assertResult(result,'https://subdomain.chargify.com/customers/123.json','GET',None)
+
+ # Read/show (via reference value)
+ result = chargify.customers.lookup.construct_request(reference=123)
+ self.assertResult(result,'https://subdomain.chargify.com/customers/lookup.json?reference=123','GET',None)
+
+ # Create
+ result = chargify.customers.create.construct_request(data={
+ 'customer':{
+ 'first_name':'Joe',
+ 'last_name':'Blow',
+ 'email':'joe@example.com'
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/customers.json','POST',
+ '{"customer": {"first_name": "Joe", "last_name": "Blow", "email": "joe@example.com"}}')
+
+ # Edit/update
+ result = chargify.customers.update.construct_request(customer_id=123,data={
+ 'customer':{
+ 'email':'joe@example.com'
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/customers/123.json','PUT',
+ '{"customer": {"email": "joe@example.com"}}')
+
+ # Delete
+ result = chargify.customers.delete.construct_request(customer_id=123)
+ self.assertResult(result,'https://subdomain.chargify.com/customers/123.json','DELETE',None)
+
+class TestProducts(ChargifyTestCase):
+
+ def test_construct_request(self):
+ chargify = Chargify('api_key','subdomain')
+
+ # List
+ result = chargify.products.construct_request()
+ self.assertResult(result,'https://subdomain.chargify.com/products.json','GET',None)
+
+ # Read/show (via chargify id)
+ result = chargify.products.construct_request(product_id=123)
+ self.assertResult(result,'https://subdomain.chargify.com/products/123.json','GET',None)
+
+ # Read/show (via api handle)
+ result = chargify.products.handle.construct_request(handle='myhandle')
+ self.assertResult(result,'https://subdomain.chargify.com/products/handle/myhandle.json','GET',None)
+
+class TestSubscriptions(ChargifyTestCase):
+
+ def test_construct_request(self):
+ chargify = Chargify('api_key','subdomain')
+
+ # List
+ result = chargify.customers.subscriptions.construct_request(customer_id=123)
+ self.assertResult(result,'https://subdomain.chargify.com/customers/123/subscriptions.json','GET',None)
+
+ # Read
+ result = chargify.subscriptions.construct_request(subscription_id=123)
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions/123.json','GET',None)
+
+ # Create
+ result = chargify.subscriptions.create.construct_request(data={
+ 'subscription':{
+ 'product_handle':'my_product',
+ 'customer_attributes':{
+ 'first_name':'Joe',
+ 'last_name':'Blow',
+ 'email':'joe@example.com'
+ },
+ 'credit_card_attributes':{
+ 'full_number':'1',
+ 'expiration_month':'10',
+ 'expiration_year':'2020'
+ }
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions.json','POST',
+ '{"subscription": {"product_handle": "my_product", "credit_card_attributes": {"expiration_month": "10", "full_number": "1", "expiration_year": "2020"}, "customer_attributes": {"first_name": "Joe", "last_name": "Blow", "email": "joe@example.com"}}}')
+
+ # Update
+ result = chargify.subscriptions.update.construct_request(data={
+ 'subscription':{
+ 'credit_card_attributes':{
+ 'full_number':'2',
+ 'expiration_month':'10',
+ 'expiration_year':'2030'
+ }
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions.json','PUT',
+ '{"subscription": {"credit_card_attributes": {"expiration_month": "10", "full_number": "2", "expiration_year": "2030"}}}')
+
+ # Delete
+ result = chargify.subscriptions.delete.construct_request(subscription_id=123,data={
+ 'subscription':{
+ 'cancellation_message':'Goodbye!'
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions/123.json','DELETE',
+ '{"subscription": {"cancellation_message": "Goodbye!"}}')
+
+class TestCharges(ChargifyTestCase):
+
+ def test_construct_request(self):
+ chargify = Chargify('api_key','subdomain')
+
+ # Create
+ result = chargify.subscriptions.charges.create.construct_request(subscription_id=123,data={
+ 'charge':{
+ 'amount':'1.00',
+ 'memo':'This is the description of the one time charge.'
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions/123/charges.json','POST',
+ '{"charge": {"amount": "1.00", "memo": "This is the description of the one time charge."}}')
+
+class TestComponents(ChargifyTestCase):
+
+ def test_construct_request(self):
+ chargify = Chargify('api_key','subdomain')
+
+ # List
+ result = chargify.subscriptions.components.usages.construct_request(subscription_id=123,component_id=456)
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions/123/components/456/usages.json','GET',None)
+
+ # Create
+ result = chargify.subscriptions.components.usages.create.construct_request(subscription_id=123,component_id=456,data={
+ 'usage':{
+ 'quantity':5,
+ 'memo':'My memo'
+ }
+ })
+ self.assertResult(result,'https://subdomain.chargify.com/subscriptions/123/components/456/usages.json','POST',
+ '{"usage": {"memo": "My memo", "quantity": 5}}')
+
+if __name__ == "__main__":
+ unittest.main()

0 comments on commit 95af393

Please sign in to comment.