diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d646835 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +__pycache__/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fa61552 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "3.4" + - "pypy" + - "pypy3" +script: nosetests diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..9561fb1 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.rst diff --git a/README.rst b/README.rst index 6e040fa..4c074aa 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,9 @@ coderwall A Python module for accessing user data at http://coderwall.com +.. image:: https://api.travis-ci.org/cwc/coderwall-python.png + :target: http://travis-ci.org/cwc/coderwall-python + Installation ------------ diff --git a/README.txt b/README.txt deleted file mode 120000 index 92cacd2..0000000 --- a/README.txt +++ /dev/null @@ -1 +0,0 @@ -README.rst \ No newline at end of file diff --git a/coderwall.py b/coderwall.py old mode 100644 new mode 100755 index c727ab2..2e430cd --- a/coderwall.py +++ b/coderwall.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ This module provides a simple interface to the API at http://coderwall.com. @@ -7,10 +8,19 @@ standalone script (i.e. 'python -m coderwall'). """ +import sys import json -import urllib2 -class CoderWall: +# Handle differences in urllib imports +if sys.version_info[0] >= 3: + import urllib.request as urllib_request + import urllib.error as urllib_error +else: + import urllib2 as urllib_request + urllib_error = urllib_request + + +class CoderWall(object): """ Represents a CoderWall user and provides access to all of the user's data. @@ -32,17 +42,17 @@ class CoderWall: >>> cwc.endorsements 0 >>> cwc.badges - [Charity: Fork and commit to someone's open source project in need, - Python: Would you expect anything less? Have at least one original repo - where Python is the dominant language, T-Rex: Have at least one original + [Charity: Fork and commit to someone's open source project in need, + Python: Would you expect anything less? Have at least one original repo + where Python is the dominant language, T-Rex: Have at least one original repo where C is the dominant language] >>> cwc.badges[0].image_uri http://cdn.coderwall.com/assets/badges/charity-bf61e713137d910534ff805f389bcffb.png >>> print cwc - Cameron Currie (cwc), Austin, TX, Endorsed 0 times: [Charity: Fork and - commit to someone's open source project in need, Python: Would you expect - anything less? Have at least one original repo where Python is the dominant - language, T-Rex: Have at least one original repo where C is the dominant + Cameron Currie (cwc), Austin, TX, Endorsed 0 times: [Charity: Fork and + commit to someone's open source project in need, Python: Would you expect + anything less? Have at least one original repo where Python is the dominant + language, T-Rex: Have at least one original repo where C is the dominant language] """ @@ -57,15 +67,17 @@ def __init__(self, username): self.endorsements = data[2] self.badges = parse_badges(data[3]) else: - raise NameError(self.username + ' does not appear to be a CoderWall user') + raise NameError(self.username + + ' does not appear to be a CoderWall user') def __repr__(self): - return self.__str__() # NOTE Does not conform to standards + return "CoderWall(username=%r)" % (self.username) + + def __str__(self): + return '%s (%s), %s, Endorsed %s times: %s' % (self.name, self.username, self.location, str(self.endorsements), str(self.badges)) - def __str__(self): - return self.name + ' (' + self.username + '), ' + self.location + ', Endorsed ' + str(self.endorsements) + ' times: ' + str(self.badges) -class Badge: +class Badge(object): """ Represents a CoderWall badge and provides access to its attributes, such as @@ -76,33 +88,35 @@ class Badge: name description image_uri - """ - + """ + def __init__(self, name, description, image_uri): self.name = name self.description = description self.image_uri = image_uri def __repr__(self): - return self.__str__() # NOTE Does not conform to standards + return "Badge(name=%r,description=%r,image_uri=%r)" % (self.name, self.description, self.image_uri) def __str__(self): return self.name + ': ' + self.description + def get_json_data(username): """ - Connect to CoderWall and return the raw JSON data for the given - username. + Connect to CoderWall and return the raw JSON data for the given + username. """ api_url = 'http://coderwall.com/' + username + '.json' try: - response = urllib2.urlopen(api_url, None, 5) - except urllib2.URLError: - return '' # TODO Better error handling + response = urllib_request.urlopen(api_url, None, 5) + except urllib_error.URLError: + return '' # TODO Better error handling + + return response.read().decode('utf-8') - return response.read() def parse_json_data(json_data): """ Parse the given JSON data and return data about the user. """ @@ -110,33 +124,36 @@ def parse_json_data(json_data): try: data = json.loads(json_data) except ValueError: - return None # TODO Better error handling + return None # TODO Better error handling name = data['name'] location = data['location'] - endorsements = data['endorsements'] + endorsements = data['endorsements'] badges = data['badges'] return (name, location, endorsements, badges) + def parse_badges(raw_badges): """ - Parse the given list of dictionaries, interpret each as a + Parse the given list of dictionaries, interpret each as a CoderWall badge, and return a list of Badge objects. """ badges = [] for raw_badge in raw_badges: - badges.append(Badge(raw_badge['name'], - raw_badge['description'], raw_badge['badge'])) + badges.append(Badge( + raw_badge['name'], + raw_badge['description'], + raw_badge['badge'] + )) return badges -if __name__ == '__main__': - import sys +if __name__ == '__main__': if len(sys.argv) < 2: - print 'Usage: ' + sys.argv[0] + ' USERNAME...' + print('Usage: ' + sys.argv[0] + ' [username ...]') else: for username in sys.argv[1:]: - print CoderWall(username) + print(CoderWall(username)) diff --git a/coderwall_test.py b/coderwall_test.py new file mode 100644 index 0000000..ef34e07 --- /dev/null +++ b/coderwall_test.py @@ -0,0 +1,87 @@ +import sys +from nose import with_setup + +import coderwall +from coderwall import CoderWall + +real_get_json_data = coderwall.get_json_data + + +def get_mock_data(*username): + return """ + { + "name": "Cameron Currie", + "location": "Texas", + "endorsements": 7, + "badges": [ + { + "name": "Test Badge 1", + "description": "A test badge 1", + "badge": "http://test.badge1" + }, + { + "name": "Test Badge 2", + "description": "A test badge 2", + "badge": "http://test.badge2" + } + ] + } + """ + + +def setUp(): + coderwall.get_json_data = get_mock_data + + +def tearDown(): + coderwall.get_json_data = real_get_json_data + + +@with_setup(setUp, tearDown) +def test_parse_json_data(): + result = coderwall.parse_json_data(get_mock_data()) + + assert result[0] == 'Cameron Currie' + assert result[1] == 'Texas' + assert result[2] == 7 + + assert len(result[3]) == 2 + assert result[3][0]['name'] == 'Test Badge 1' + assert result[3][0]['badge'] == 'http://test.badge1' + + +@with_setup(setUp, tearDown) +def test_parse_badges(): + raw_badges = coderwall.parse_json_data(get_mock_data())[3] + badges = coderwall.parse_badges(raw_badges) + + assert len(badges) == 2 + assert badges[0].name == 'Test Badge 1' + assert badges[0].image_uri == 'http://test.badge1' + + +@with_setup(setUp, tearDown) +def test_coderwall_parsing(): + cwc = CoderWall('cwc') + + assert cwc.username == 'cwc' + assert cwc.location == 'Texas' + assert cwc.endorsements == 7 + + assert len(cwc.badges) == 2 + assert cwc.badges[0].name == 'Test Badge 1' + assert cwc.badges[0].image_uri == 'http://test.badge1' + + +@with_setup(setUp, tearDown) +def test_coderwall_to_str(): + cwc = CoderWall('cwc') + expStr2 = """Cameron Currie (cwc), Texas, Endorsed 7 times: [Badge(name=u'Test Badge 1',description=u'A test badge 1',image_uri=u'http://test.badge1'), Badge(name=u'Test Badge 2',description=u'A test badge 2',image_uri=u'http://test.badge2')]""" + expStr3 = """Cameron Currie (cwc), Texas, Endorsed 7 times: [Badge(name='Test Badge 1',description='A test badge 1',image_uri='http://test.badge1'), Badge(name='Test Badge 2',description='A test badge 2',image_uri='http://test.badge2')]""" + + if sys.version_info[0] >= 3: + expStr = expStr3 + else: + expStr = expStr2 + + assert str(cwc) == expStr, "str(cwc) was: %s, expected: %s" % (str(cwc), expStr) diff --git a/setup.py b/setup.py index 434daaa..3fbbf62 100644 --- a/setup.py +++ b/setup.py @@ -2,22 +2,32 @@ setup( name='coderwall', - version='1.0.0', + version='1.3.0', author='Cameron Currie', author_email='me@cameroncurrie.net', url='http://github.com/cwc/coderwall-python', description='A Python module for accessing user data at http://coderwall.com', - long_description=open('README.txt').read(), + long_description=open('README.rst').read(), license='MIT', classifiers=[ 'Programming Language :: Python', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Development Status :: 5 - Production/Stable', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Development Status :: 6 - Mature', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules' + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Internet', + 'Topic :: Internet :: WWW/HTTP', + 'Environment :: Console', + 'Environment :: Web Environment' ], py_modules=['coderwall'] ) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..6b8408f --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +nose==1.3.7 +wheel==0.38.1