├── behance_python ├── test │ ├── __init__.py │ └── test.py ├── __init__.py ├── exceptions.py ├── collection.py ├── wip.py ├── project.py ├── behance.py ├── user.py └── api.py ├── requirements.txt ├── MANIFEST ├── setup.py ├── .gitignore ├── CHANGES.txt ├── LICENSE └── README.md /behance_python/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=1.0.0 2 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | behance_python/__init__.py 4 | behance_python/api.py 5 | behance_python/behance.py 6 | behance_python/collection.py 7 | behance_python/exceptions.py 8 | behance_python/project.py 9 | behance_python/user.py 10 | behance_python/wip.py 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='behance_python', 5 | packages=['behance_python',], 6 | install_requires=['requests'], 7 | url='https://github.com/aravenel/behance_python', 8 | license='MIT', 9 | author='Alex Ravenel', 10 | author_email='ravenel@gmail.com', 11 | version='0.3.1', 12 | ) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | 29 | *.swp 30 | venv 31 | .venv 32 | .venv2 33 | .idea 34 | env.sh -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.1.0, Sept 25 2012 -- Initial release 2 | v0.2.0, Oct 5 2012 -- Updated to allow use of object or dict notation when accessing attributes 3 | v0.2.1, Oct 5 2012 -- Tweaks to setup.py 4 | v0.2.2, Oct 5 2012 -- Added URL to setup.py 5 | v0.2.3, Jan 5 2012 -- Added user stats, follower, following, feedback, and work experience endpoints 6 | v0.2.4, Jan 6 2012 -- Updated to support and require Requests >=1.0.0 7 | v0.2.5, Oct 20 2014 -- Removed feedback circle support 8 | -------------------------------------------------------------------------------- /behance_python/__init__.py: -------------------------------------------------------------------------------- 1 | ENDPOINTS = { 2 | 'api': 'http://www.behance.net/v2', 3 | 'project': '/projects', 4 | 'user': '/users', 5 | 'wip': '/wips', 6 | 'collection': '/collections', 7 | } 8 | 9 | def url_join(*args): 10 | return "/".join(str(s).strip('/') for s in args) 11 | 12 | from behance_python.api import * 13 | from behance_python.project import * 14 | from behance_python.exceptions import * 15 | from behance_python.behance import * 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Alex Ravenel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /behance_python/exceptions.py: -------------------------------------------------------------------------------- 1 | EXCEPTIONMAPPING = { 2 | 403: 'Forbidden', 3 | 404: 'NotFound', 4 | 429: 'TooManyRequests', 5 | 500: 'InternalServerError', 6 | 503: 'ServiceUnavailable', 7 | } 8 | 9 | class BehanceException(Exception): 10 | """Base exception. 11 | 12 | Has two attributes: 13 | self.error_code: HTTP error code as returned by Behance API 14 | self.msg: message to be printed referencing error code.""" 15 | def __init__(self, error_code=None): 16 | self.error_code = error_code 17 | if self.error_code: 18 | self.msg = "Behance API threw a %s error." % error_code 19 | else: 20 | self.msg = "Behance API had an unknown error." 21 | def __str__(self): 22 | return repr(self.msg) 23 | 24 | class Forbidden(BehanceException): 25 | pass 26 | 27 | class NotFound(BehanceException): 28 | pass 29 | 30 | class TooManyRequests(BehanceException): 31 | pass 32 | 33 | class InternalServerError(BehanceException): 34 | pass 35 | 36 | class ServiceUnavailable(BehanceException): 37 | pass 38 | -------------------------------------------------------------------------------- /behance_python/collection.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | from behance_python.behance import Behance 3 | from behance_python import ENDPOINTS, url_join 4 | 5 | class Collection(Behance): 6 | 7 | def __init__(self, collection_id, auth_key): 8 | Behance.__init__(self, auth_key) 9 | self.collection_id = collection_id 10 | self.base_url = url_join(ENDPOINTS['api'], ENDPOINTS['collection']) 11 | 12 | self._get_collection_details() 13 | 14 | def _get_collection_details(self): 15 | _url = url_join(self.base_url, str(self.collection_id)) 16 | _url = "%s?api_key=%s" % (_url, self.auth_key) 17 | 18 | _results = self._get_api_data(_url)['collection'] 19 | self.set_data(_results) 20 | 21 | def get_projects(self, **kwargs): 22 | _base_url = url_join(self.base_url, self.collection_id, 'projects') 23 | if len(kwargs) > 0: 24 | _filters = urllib.urlencode(kwargs) 25 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 26 | else: 27 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 28 | 29 | return self._parse_data(self._get_api_data(_url)['projects']) 30 | -------------------------------------------------------------------------------- /behance_python/wip.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | from behance_python import ENDPOINTS, url_join 3 | from behance_python.behance import Behance 4 | 5 | class WIP(Behance): 6 | 7 | def __init__(self, wip_id, auth_key): 8 | #super(Behance, self).__init__(auth_key) 9 | Behance.__init__(self, auth_key) 10 | self.wip_id = wip_id 11 | self.base_url = url_join(ENDPOINTS['api'], ENDPOINTS['wip']) 12 | 13 | self._get_wip_details() 14 | 15 | def _get_wip_details(self): 16 | _url = url_join(self.base_url, self.wip_id) 17 | _url = '%s?api_key=%s' % (_url, self.auth_key) 18 | _results = self._get_api_data(_url)['wip'] 19 | self.set_data(_results) 20 | 21 | def get_revision(self, revision_id): 22 | _url = url_join(self.base_url, self.wip_id, str(revision_id)) 23 | _url = '%s?api_key=%s' % (_url, self.auth_key) 24 | return self._parse_data(self._get_api_data(_url)['revision']) 25 | 26 | def get_revision_comments(self, revision_id, **kwargs): 27 | _base_url = url_join(self.base_url, self.wip_id, str(revision_id), '/comments') 28 | if len(kwargs) > 0: 29 | _filters = urllib.urlencode(kwargs) 30 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 31 | else: 32 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 33 | 34 | return self._parse_data(self._get_api_data(_url)['comments']) 35 | -------------------------------------------------------------------------------- /behance_python/project.py: -------------------------------------------------------------------------------- 1 | from behance_python.behance import Behance 2 | from behance_python import ENDPOINTS, url_join 3 | 4 | class Project(Behance): 5 | """Class representing a Behance project.API_ENDPOINT 6 | 7 | When instantiated, will call API. Instance will have attributes with names 8 | and values equal to JSON key/values returned from API. 9 | """ 10 | 11 | def __init__(self, project_id, auth_key): 12 | Behance.__init__(self, auth_key) 13 | self.project_id = project_id 14 | self.base_url = url_join(ENDPOINTS['api'], ENDPOINTS['project']) 15 | 16 | #Call behance API and populate data 17 | self._get_project_details() 18 | 19 | def _get_project_details(self): 20 | """Internal function to call Behance API and parse data.""" 21 | #Build the URL 22 | _url = url_join(self.base_url, str(self.project_id)) 23 | _url = "%s?api_key=%s" % (_url, self.auth_key) 24 | #Call the API 25 | _results = self._get_api_data(_url)['project'] 26 | self.set_data(_results) 27 | 28 | def get_comments(self): 29 | """Returns comments for a project as an attribute. If called more than 30 | once, will store the value to prevent repeatedly calling the API.""" 31 | _url = url_join(self.base_url, str(self.project_id), 'comments') 32 | _url = "%s?api_key=%s" % (_url, self.auth_key) 33 | return self._parse_data(self._get_api_data(_url)['comments']) 34 | -------------------------------------------------------------------------------- /behance_python/behance.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import behance_python.exceptions 3 | from requests.exceptions import ConnectionError, HTTPError, Timeout, TooManyRedirects 4 | 5 | class Behance(dict): 6 | """Base class to be inherited by other Behance classes (project, user, WIP, 7 | collection). Implements API calling and error handling.""" 8 | 9 | def __init__(self, auth_key=None, data=None): 10 | if auth_key: 11 | self.auth_key = auth_key 12 | if data: 13 | self.set_data(data) 14 | 15 | def _add_property(self, name, value): 16 | """Helper function to dynamically add all the JSON data from API response 17 | to the Project object.""" 18 | setattr(self.__class__, name, value) 19 | 20 | def _convert_int(self, value): 21 | """Handle JSON integer keys stored as strings""" 22 | try: 23 | return int(value) 24 | except ValueError: 25 | return value 26 | 27 | def _parse_data(self, data): 28 | """Recursively process data to allow object notation""" 29 | if isinstance(data, dict): 30 | new_data = Behance(data=data) 31 | elif isinstance(data, list): 32 | new_data = [self._parse_data(item) for item in data] 33 | else: 34 | new_data = data 35 | 36 | return new_data 37 | 38 | def _get_api_data(self, url): 39 | """Internal helper to call API and handle exceptions""" 40 | try: 41 | _results = requests.get(url) 42 | 43 | if _results.status_code == 200: 44 | return _results.json() 45 | else: 46 | #Throw the error corresponding to the correct error code. 47 | #If unknown error code, throw generic error. 48 | n = _results.status_code 49 | try: 50 | raise getattr(behance_python.exceptions, behance_python.exceptions.EXCEPTIONMAPPING[n])(n) 51 | except AttributeError: 52 | raise behance_python.exceptions.BehanceException(n) 53 | except (ConnectionError, HTTPError, Timeout, TooManyRedirects) as e: 54 | #If requests raises an exception 55 | raise e 56 | 57 | def set_data(self, data): 58 | """Set data after parsing.""" 59 | if isinstance(data, dict): 60 | for k, v in data.items(): 61 | new_k = self._convert_int(k) 62 | new_v = self._parse_data(v) 63 | self.__setattr__(new_k, new_v) 64 | else: 65 | raise TypeError('Expected a dict') 66 | 67 | __getattr__ = dict.__getitem__ 68 | __setattr__ = dict.__setitem__ 69 | __delattr__ = dict.__delitem__ 70 | -------------------------------------------------------------------------------- /behance_python/user.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | from behance_python.behance import Behance 3 | from behance_python import ENDPOINTS, url_join 4 | 5 | # TODO: Should get_projects return project objects? How to do this with min overhead? 6 | 7 | class User(Behance): 8 | 9 | def __init__(self, user_id, auth_key): 10 | Behance.__init__(self, auth_key) 11 | self.user_id = user_id 12 | self.base_url = url_join(ENDPOINTS['api'], ENDPOINTS['user']) 13 | 14 | self._get_user_details() 15 | 16 | def _get_user_details(self): 17 | #Build the URL 18 | _url = url_join(self.base_url, str(self.user_id)) 19 | _url = "%s?api_key=%s" % (_url, self.auth_key) 20 | 21 | #Call the API 22 | _results = self._get_api_data(_url)['user'] 23 | self.set_data(_results) 24 | 25 | def get_projects(self, **kwargs): 26 | 27 | _base_url = url_join(self.base_url, self.user_id, 'projects') 28 | if len(kwargs) > 0: 29 | _filters = urllib.urlencode(kwargs) 30 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 31 | else: 32 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 33 | 34 | return self._parse_data(self._get_api_data(_url)['projects']) 35 | 36 | def get_wips(self, **kwargs): 37 | _base_url = url_join(self.base_url, self.user_id, 'wips') 38 | if len(kwargs) > 0: 39 | _filters = urllib.urlencode(kwargs) 40 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 41 | else: 42 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 43 | 44 | return self._parse_data(self._get_api_data(_url)['wips']) 45 | 46 | def get_appreciations(self, **kwargs): 47 | _base_url = url_join(self.base_url, self.user_id, 'appreciations') 48 | if len(kwargs) > 0: 49 | _filters = urllib.urlencode(kwargs) 50 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 51 | else: 52 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 53 | 54 | return self._parse_data(self._get_api_data(_url)['appreciations']) 55 | 56 | def get_collections(self, **kwargs): 57 | _base_url = url_join(self.base_url, self.user_id, 'collections') 58 | if len(kwargs) > 0: 59 | _filters = urllib.urlencode(kwargs) 60 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 61 | else: 62 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 63 | 64 | return self._parse_data(self._get_api_data(_url)['collections']) 65 | 66 | def get_stats(self): 67 | _base_url = url_join(self.base_url, self.user_id, 'stats') 68 | _url = "%s?api_key=%s" % (_base_url, self.auth_key) 69 | 70 | return self._parse_data(self._get_api_data(_url)['stats']) 71 | 72 | def get_followers(self, **kwargs): 73 | _base_url = url_join(self.base_url, self.user_id, 'followers') 74 | if len(kwargs) > 0: 75 | _filters = urllib.urlencode(kwargs) 76 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 77 | else: 78 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 79 | 80 | return self._parse_data(self._get_api_data(_url)['followers']) 81 | 82 | def get_following(self, **kwargs): 83 | _base_url = url_join(self.base_url, self.user_id, 'following') 84 | if len(kwargs) > 0: 85 | _filters = urllib.urlencode(kwargs) 86 | _url = '%s?api_key=%s&%s' % (_base_url, self.auth_key, _filters) 87 | else: 88 | _url = '%s?api_key=%s' % (_base_url, self.auth_key) 89 | 90 | return self._parse_data(self._get_api_data(_url)['following']) 91 | 92 | def get_work_experience(self): 93 | _base_url = url_join(self.base_url, self.user_id, 'work_experience') 94 | _url = "%s?api_key=%s" % (_base_url, self.auth_key) 95 | 96 | return self._parse_data(self._get_api_data(_url)['work_experience']) 97 | -------------------------------------------------------------------------------- /behance_python/api.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | import requests 3 | from behance_python import ENDPOINTS, url_join 4 | from behance_python.project import Project 5 | from behance_python.user import User 6 | from behance_python.wip import WIP 7 | from behance_python.collection import Collection 8 | from behance_python.behance import Behance 9 | import behance_python.exceptions 10 | from requests.exceptions import ConnectionError, HTTPError, Timeout, TooManyRedirects 11 | 12 | class API: 13 | """Base wrapper for the Behance api. 14 | 15 | Must be instantiated using your provided auth key.""" 16 | 17 | def __init__(self, auth_key): 18 | self.auth_key = auth_key 19 | 20 | def _do_api_search(self, url): 21 | try: 22 | #Get results from API 23 | _results = requests.get(url) 24 | 25 | #Parse results 26 | if _results.status_code == 200: 27 | return _results.json() 28 | else: 29 | n = _results.status_code 30 | try: 31 | raise getattr(behance_python.exceptions, behance_python.exceptions.EXCEPTIONMAPPING[n])(n) 32 | except AttributeError: 33 | raise behance_python.exceptions.BehanceException(n) 34 | except (ConnectionError, HTTPError, Timeout, TooManyRedirects) as e: 35 | raise e 36 | 37 | 38 | def get_project(self, project_id): 39 | """Query behance API and return Project instance""" 40 | return Project(project_id, self.auth_key) 41 | 42 | def project_search(self, *args, **kwargs): 43 | """Search for projects on Behance. 44 | Takes any number of text search terms, as well as key/value filters. 45 | 46 | Valid filters: [valid values] 47 | sort: [featured_date, appreciations, views, comments, published_date] 48 | time: [all, today, week, month] 49 | field: [URL-encoded field name from Behance list of defined creative fields] 50 | country: [2-letter FIPS country code] 51 | state: [State or province name] 52 | page: [page number of results, 1-indexed] 53 | tags: [single tag name or pipe separated list of tags] 54 | """ 55 | if len(args) == 0: 56 | #Make sure user provides search terms... 57 | return None 58 | else: 59 | #Build the URL 60 | _base_url = url_join(ENDPOINTS['api'], ENDPOINTS['project']) 61 | _terms = "+".join(urllib.parse.quote(arg) for arg in args) 62 | _filters = urllib.parse.urlencode(kwargs) 63 | _url = '%s?api_key=%s&q=%s&%s' % (_base_url, self.auth_key, _terms, _filters) 64 | 65 | #Get results from API 66 | return [Behance(data=proj) for proj in self._do_api_search(_url)['projects']] 67 | 68 | def user_search(self, *args, **kwargs): 69 | """Search for users on Behance. 70 | Takes any number of text search terms, as well as key/value filters 71 | as supported by Behance API.""" 72 | if len(args) == 0: 73 | return None 74 | else: 75 | _base_url = url_join(ENDPOINTS['api'], ENDPOINTS['user']) 76 | _terms = "+".join(urllib.parse.quote(arg) for arg in args) 77 | _filters = urllib.parse.urlencode(kwargs) 78 | _url = '%s?api_key=%s&q=%s&%s' % (_base_url, self.auth_key, _terms, _filters) 79 | 80 | #Get results from API 81 | return [Behance(data=user) for user in self._do_api_search(_url)['users']] 82 | 83 | def get_user(self, user_id): 84 | return User(user_id, self.auth_key) 85 | 86 | def wip_search(self, *args, **kwargs): 87 | if len(args) == 0: 88 | return None 89 | else: 90 | _base_url = url_join(ENDPOINTS['api'], ENDPOINTS['wip']) 91 | _terms = "+".join(urllib.parse.quote(arg) for arg in args) 92 | _filters = urllib.parse.urlencode(kwargs) 93 | _url = '%s?api_key=%s&q=%s&%s' % (_base_url, self.auth_key, _terms, _filters) 94 | 95 | #Get results from API 96 | return [Behance(data=wip) for wip in self._do_api_search(_url)['wips']] 97 | 98 | def get_wip(self, wip_id): 99 | return WIP(wip_id, self.auth_key) 100 | 101 | def collection_search(self, *args, **kwargs): 102 | if len(args) == 0: 103 | return None 104 | else: 105 | _base_url = url_join(ENDPOINTS['api'], ENDPOINTS['collection']) 106 | _terms = "+".join(urllib.parse.quote(arg) for arg in args) 107 | _filters = urllib.parse.urlencode(kwargs) 108 | _url = '%s?api_key=%s&q=%s&%s' % (_base_url, self.auth_key, _terms, _filters) 109 | 110 | #Get results from API 111 | return [Behance(data=collection) for collection in self._do_api_search(_url)['collections']] 112 | 113 | def get_collection(self, collection_id): 114 | return Collection(collection_id, self.auth_key) 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | behance_python 2 | ============== 3 | 4 | A Python wrapper for the Behance API 5 | ------------------------------------ 6 | 7 | ####Warning! This wrapper is very much still in development and could change substantially! 8 | 9 | ####Note that this library does not currently support any of the OAUTH based POST functionality. This functionality will be added in future releases. 10 | 11 | Please see [Behance API documentation](http://www.behance.net/dev) to get an 12 | API key and more information, including field names and example content. 13 | 14 | **Note**: Many Behance API endpoints provide data in pages of 12 items. You will 15 | need to page through results until you receive a 500 error (in the form of 16 | a BehanceException) which will indicate that there are no more pages. 17 | 18 | #Installation 19 | pip install behance_python 20 | 21 | Or: 22 | 23 | git clone git://github.com/aravenel/behance_python.git 24 | 25 | #Usage 26 | All wrapper functionality flows from the main API object which must be 27 | instantiated using your Behance-provided API Key. 28 | 29 | All attributes can be accessed using either objects (object.key) or dict 30 | (object['key']) notation. **Beware!** Some of the JSON returned may have numerical 31 | keys, which Python cannot use for object notation--you will need to access these 32 | by their dict notation. Additionally, as JSON returns unicode, integer keys have 33 | been converted from unicode to ints to make life easier. 34 | 35 | ##API Object Usage 36 | ```python 37 | from behance_python.api import API 38 | 39 | behance = API('your_api_key_here') 40 | ``` 41 | 42 | ##Project functionality 43 | ###Search for projects 44 | ```python 45 | projects = behance.project_search('term1', 'term2', filter_key='filter_value') 46 | projects[0].owners[129052].first_name 47 | >>> 'Matias' 48 | ``` 49 | 50 | Supports all filters and modifiers as supported by Behance. 51 | 52 | Data will be returned as list of objects with same keys as Behance API. To save 53 | on API calls, these are not full Project objects (i.e. they do not have all of 54 | the attributes you would get from get_project())--you must call the 55 | API.get_project(project_id) method to get project details including images. 56 | 57 | ###Get Single Project Details 58 | ```python 59 | proj = behance.get_project(project_id) 60 | project_images = [module.src for module in proj.modules if module.type=='image'] 61 | len(project_images) 62 | >>> 3 63 | ``` 64 | 65 | Returns an instance of the Project object. This object has attributes named 66 | identically to attributes as returned by Behance API. As with the API, 67 | artwork associated with a project are stored in Project.modules, which is a list 68 | of objects, each object representing one module and its corresponding 69 | metadata. 70 | 71 | ###Get Project Comments 72 | ```python 73 | comments = proj.get_comments() 74 | comment[0].user.first_name 75 | >>> 'Matias' 76 | ``` 77 | Method of the Project object. Returns list of objects, each object 78 | representing a single comment and its metadata. 79 | 80 | ##User functionality 81 | ###Search for Users 82 | ```python 83 | users = behance.user_search('term1', 'term2', filter_key='filter_value') 84 | users[0].first_name 85 | >>> 'Matias' 86 | ``` 87 | Works just like project_search. 88 | 89 | ###Get Single User Details 90 | ```python 91 | user = behance.get_user(user_id_or_username) 92 | user.first_name 93 | >>> 'Matias' 94 | ``` 95 | Returns User object. This object has attributes named identically to attributes 96 | as returned by Behance API. 97 | 98 | 99 | ###Get User Projects 100 | ```python 101 | user_projects = user.get_projects(filter_key='filter_value') 102 | user_projects[0].name 103 | >>> 'The ALVA Award' 104 | ``` 105 | Method of the User object. Returns list of objects. Can optionally include any 106 | filters supported by Behance API. So as not to chew up API calls, these are not 107 | actual Project objects. To get artwork associated with these projects, you will 108 | need to call the API.get_project(project_id) method. 109 | 110 | ###Get User Works in Progress 111 | ```python 112 | user_wips = user.get_wips(filter_key='filter_value') 113 | wips[0].latest_rev_id 114 | >>> '173' 115 | ``` 116 | Method of the User object. Returns list of objects. Can optionally include any 117 | filters supported by Behance API. So as not to chew up API calls, these are not 118 | actual WIP objects. To get artwork associated with these projects, you will 119 | need to call the API.get_WIP(wip_id) method. 120 | 121 | ###Get User Appreciations 122 | ```python 123 | user_appreciations = user.get_appreciations(filter_key='filter_value') 124 | user_appreciations[0].project.id 125 | >>> 4979439 126 | ``` 127 | Method of the User object. Can optionally include any filters supported by Behance API. 128 | Returns list of objects. 129 | 130 | ###Get User Collections 131 | ```python 132 | user_collections = user.get_collections(filter_key='filter_value') 133 | user_collections[0].title 134 | >>> '00 Curation' 135 | ``` 136 | Method of the User object. Can optionally include any filters supported by Behance API. 137 | Returns list of objects. 138 | 139 | ###Get User Stats 140 | ```python 141 | user_stats = user.get_stats() 142 | user_stats.today.project_views 143 | >>> 220 144 | ``` 145 | Method of the User object. 146 | 147 | ###Get User Followers 148 | ```python 149 | followers = user.get_followers(filter_key='filter_value') 150 | followers[0].username 151 | >>> 'getflourish' 152 | ``` 153 | Method of the User object. Can optionally include any filters supported by Behance API. 154 | Returns list of objects. 155 | 156 | ###Get Users Being Followed 157 | ```python 158 | following = user.get_following(filter_key='filter_value') 159 | following[0].username 160 | >>> 'getflourish' 161 | ``` 162 | Method of the User object. Can optionally include any filters supported by Behance API. 163 | Returns list of objects. 164 | 165 | ###Get Work Experience 166 | ```python 167 | work_experience = user.get_work_experience() 168 | work_experience[0].position 169 | >>> 'Senior Designer' 170 | ``` 171 | Method of the User object. Returns list of objects. 172 | 173 | ##Work in Progress Functionality 174 | ###Search for Works in Progress 175 | ```python 176 | wips = behance.wip_search('term1', 'term2', filter_key='filter_value') 177 | wips[0].title 178 | >>> 'Restaurant Menu Cover' 179 | ``` 180 | Works just like project_search. 181 | 182 | ###Get Work in Progress 183 | ```python 184 | wip = behance.get_wip(wip_id) 185 | wip.title 186 | >>> 'Portfolio Review Schedule Design' 187 | ``` 188 | Returns WIP object. This object has attributes named identically to attributes 189 | as returned by Behance API. 190 | 191 | ###Get Revision 192 | ```python 193 | rev = wip.get_revision(revision_id) 194 | rev.tags 195 | >>> ['behance', 'schedule', 'information'] 196 | ``` 197 | Works in progress store multiple revisions. Fetch individidual revisions with 198 | a revision ID. Method of the WIP object. 199 | 200 | ###Get Comments 201 | ```python 202 | comments = wip.get_revision_comments(revision_id, filter_key='filter_value') 203 | comments[0].user.first_name 204 | >>> 'Matias' 205 | ``` 206 | Comments are stored associated with a revision. Method of the WIP object. Can optionally 207 | include any filters supported by Behance API. Returns list of objects. 208 | 209 | ##Collection Functionality 210 | ###Search for Collections 211 | ```python 212 | collections = behance.collection_search('term1', 'term2', filter_key='filter_value') 213 | collections[0].title 214 | >>> "Candy Landia" 215 | ``` 216 | Works just like project_search. 217 | 218 | ###Get Collection 219 | ```python 220 | collection = behance.get_collection(collection_id) 221 | collection.title 222 | >>> 'Cool Candy Related Projects.' 223 | ``` 224 | Returns Collection object. This object has attributes named identically to attributes 225 | as returned by Behance API. 226 | 227 | ###Get Collection Projects 228 | ```python 229 | projects = collection.get_projects(filter_key='filter_value') 230 | projects[0].name 231 | >>> 'Sweets' 232 | ``` 233 | Returns list of projects that are members of a collection. Note that these are not actual 234 | Project instances to save on API calls--to get artwork for these, you would need to 235 | call API.get_project(project_id). Can optionally include any filters supported by 236 | Behance API. 237 | 238 | 239 | #Exceptions 240 | Unfortunately, they happen. If an exception happens in the calling of the API 241 | but before a response is returned (e.g. a timeout), the library will raise 242 | the exception from the underlying Requests library. If the response from the 243 | Behance API is anything other than status code 200, it will raise an exception 244 | corresponding to the error code. These exceptions are all subclasses of the 245 | BehanceException, and they are: 246 | - Forbidden (403 error) 247 | - NotFound (404 error) 248 | - TooManyRequests (423 error) 249 | - InternalServerError (500 error) 250 | - ServiceUnavailable (503 error) 251 | 252 | If any other error code is received, will throw a generic BehanceException with 253 | the actual error code stored in attribute self.error_code. 254 | -------------------------------------------------------------------------------- /behance_python/test/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | from behance_python.api import API 4 | from behance_python.exceptions import * 5 | 6 | """ 7 | TESTS 8 | 9 | To run tests: 10 | 1: Set environment variable with your API key: 11 | export BEHANCE_API_KEY=your_api_key_here 12 | 2: Run test: 13 | python -m behance_python.test.test 14 | """ 15 | 16 | API_KEY = os.environ.get('BEHANCE_API_KEY') 17 | PROJECT_ID = 5287059 18 | USER_NAME = 'MatiasCorea' 19 | WIP_ID = 77 20 | WIP_REVISION_ID = 177 21 | COLLECTION_ID = 14373 22 | 23 | #Attributes to check existence of in returned values 24 | #These keys should exist in both "stub" (from search functions) and "full" 25 | #(from get_XXXX functions) values 26 | PROJECT_KEYS = ['id', 'name', 'published_on'] 27 | USER_KEYS = ['id', 'username', 'created_on'] 28 | WIP_KEYS = ['id', 'title', 'url'] 29 | COLLECTION_KEYS = ['id', 'owners', 'stats'] 30 | 31 | class BehanceTestCase(unittest.TestCase): 32 | def setUp(self): 33 | self.api = API(API_KEY) 34 | 35 | class TestProject(BehanceTestCase): 36 | 37 | def test_search(self): 38 | projects = self.api.project_search('apple') 39 | self.assertEqual(len(projects), 12) 40 | for project in projects: 41 | for key in PROJECT_KEYS: 42 | self.assertTrue(project.has_key(key)) 43 | self.assertTrue(hasattr(project, key)) 44 | 45 | def test_project(self): 46 | project = self.api.get_project(PROJECT_ID) 47 | self.assertIsNotNone(project) 48 | for key in PROJECT_KEYS: 49 | self.assertTrue(hasattr(project, key)) 50 | self.assertTrue(project.has_key(key)) 51 | 52 | def test_comments(self): 53 | project = self.api.get_project(PROJECT_ID) 54 | comments = project.get_comments() 55 | self.assertGreater(len(comments), 1) 56 | for comment in comments: 57 | for key in ['user', 'comment']: 58 | self.assertTrue(comment.has_key(key)) 59 | self.assertTrue(hasattr(comment, key)) 60 | 61 | def test_exception(self): 62 | with self.assertRaises(NotFound): 63 | project = self.api.get_project(1234) 64 | with self.assertRaises(Forbidden): 65 | self.api = API('12345') 66 | projs = self.api.project_search('apple') 67 | 68 | class TestUser(BehanceTestCase): 69 | 70 | def test_search(self): 71 | users = self.api.user_search('alex') 72 | self.assertEqual(len(users), 12) 73 | for user in users: 74 | for key in USER_KEYS: 75 | self.assertTrue(user.has_key(key)) 76 | self.assertTrue(hasattr(user, key)) 77 | 78 | def test_user(self): 79 | user = self.api.get_user(USER_NAME) 80 | self.assertIsNotNone(user) 81 | for key in USER_KEYS: 82 | self.assertTrue(hasattr(user, key)) 83 | self.assertTrue(user.has_key(key)) 84 | 85 | def test_user_projects(self): 86 | user = self.api.get_user(USER_NAME) 87 | projects = user.get_projects() 88 | self.assertIsNotNone(projects) 89 | for project in projects: 90 | for key in PROJECT_KEYS: 91 | self.assertTrue(project.has_key(key)) 92 | self.assertTrue(hasattr(project, key)) 93 | 94 | def test_user_wips(self): 95 | user = self.api.get_user(USER_NAME) 96 | wips = user.get_wips() 97 | self.assertIsNotNone(wips) 98 | for wip in wips: 99 | for key in WIP_KEYS: 100 | self.assertTrue(wip.has_key(key)) 101 | self.assertTrue(hasattr(wip, key)) 102 | 103 | def test_user_appreciations(self): 104 | user = self.api.get_user(USER_NAME) 105 | appreciations = user.get_appreciations() 106 | self.assertIsNotNone(appreciations) 107 | for appreciation in appreciations: 108 | for key in ['project', 'timestamp']: 109 | self.assertTrue(appreciation.has_key(key)) 110 | self.assertTrue(hasattr(appreciation, key)) 111 | 112 | def test_user_collections(self): 113 | user = self.api.get_user(USER_NAME) 114 | collections = user.get_collections() 115 | self.assertIsNotNone(collections) 116 | for collection in collections: 117 | for key in COLLECTION_KEYS: 118 | self.assertTrue(collection.has_key(key)) 119 | self.assertTrue(hasattr(collection, key)) 120 | 121 | def test_user_stats(self): 122 | user = self.api.get_user(USER_NAME) 123 | stats = user.get_stats() 124 | self.assertIsNotNone(stats) 125 | self.assertTrue(stats.has_key('today')) 126 | self.assertTrue(stats.has_key('all_time')) 127 | self.assertTrue(hasattr(stats, 'today')) 128 | self.assertTrue(hasattr(stats, 'all_time')) 129 | 130 | def test_user_followers(self): 131 | user = self.api.get_user(USER_NAME) 132 | followers = user.get_followers() 133 | self.assertIsNotNone(followers) 134 | for follower in followers: 135 | self.assertTrue(follower.has_key('id')) 136 | self.assertTrue(follower.has_key('username')) 137 | self.assertTrue(hasattr(follower, 'id')) 138 | self.assertTrue(hasattr(follower, 'username')) 139 | 140 | def test_user_following(self): 141 | user = self.api.get_user(USER_NAME) 142 | following = user.get_following() 143 | self.assertIsNotNone(following) 144 | for follow in following: 145 | self.assertTrue(follow.has_key('id')) 146 | self.assertTrue(follow.has_key('username')) 147 | self.assertTrue(hasattr(follow, 'id')) 148 | self.assertTrue(hasattr(follow, 'username')) 149 | 150 | def test_user_work_experience(self): 151 | user = self.api.get_user(USER_NAME) 152 | work_experience = user.get_work_experience() 153 | WE_KEYS = ['position', 'organization', 'location'] 154 | for we in work_experience: 155 | for key in WE_KEYS: 156 | self.assertTrue(we.has_key(key)) 157 | self.assertTrue(hasattr(we, key)) 158 | 159 | def test_exception(self): 160 | with self.assertRaises(NotFound): 161 | user = self.api.get_user('asdf1234') 162 | with self.assertRaises(Forbidden): 163 | self.api = API('12345') 164 | users = self.api.user_search('apple') 165 | 166 | class TestWIP(BehanceTestCase): 167 | 168 | def test_search(self): 169 | wips = self.api.wip_search('apple') 170 | self.assertEqual(len(wips), 12) 171 | for wip in wips: 172 | for key in WIP_KEYS: 173 | self.assertTrue(wip.has_key(key)) 174 | self.assertTrue(hasattr(wip, key)) 175 | 176 | def test_wip(self): 177 | wip = self.api.get_wip(WIP_ID) 178 | self.assertIsNotNone(wip) 179 | for key in WIP_KEYS: 180 | self.assertTrue(hasattr(wip, key)) 181 | self.assertTrue(wip.has_key(key)) 182 | 183 | def test_revision(self): 184 | wip = self.api.get_wip(WIP_ID) 185 | revision = wip.get_revision(WIP_REVISION_ID) 186 | for key in ['id', 'description', 'url']: 187 | self.assertTrue(revision.has_key(key)) 188 | self.assertTrue(hasattr(revision, key)) 189 | 190 | def test_comments(self): 191 | wip = self.api.get_wip(WIP_ID) 192 | comments = wip.get_revision_comments(WIP_REVISION_ID) 193 | for comment in comments: 194 | for key in ['user', 'comment', 'created_on']: 195 | self.assertTrue(comment.has_key(key)) 196 | self.assertTrue(hasattr(comment, key)) 197 | 198 | def test_exception(self): 199 | with self.assertRaises(NotFound): 200 | wip = self.api.get_wip('asdf1234') 201 | with self.assertRaises(Forbidden): 202 | self.api = API('12345') 203 | wips = self.api.wip_search('apple') 204 | 205 | class TestCollection(BehanceTestCase): 206 | 207 | def test_search(self): 208 | collections = self.api.collection_search('apple') 209 | self.assertGreaterEqual(len(collections), 1) 210 | for collection in collections: 211 | for key in COLLECTION_KEYS: 212 | self.assertTrue(collection.has_key(key)) 213 | self.assertTrue(hasattr(collection, key)) 214 | 215 | def test_collection(self): 216 | collection = self.api.get_collection(COLLECTION_ID) 217 | self.assertIsNotNone(collection) 218 | for key in COLLECTION_KEYS: 219 | self.assertTrue(hasattr(collection, key)) 220 | self.assertTrue(collection.has_key(key)) 221 | 222 | def test_collection_projects(self): 223 | collection = self.api.get_collection(COLLECTION_ID) 224 | projects = collection.get_projects() 225 | for project in projects: 226 | for key in PROJECT_KEYS: 227 | self.assertTrue(project.has_key(key)) 228 | self.assertTrue(hasattr(project, key)) 229 | 230 | def test_exception(self): 231 | with self.assertRaises(NotFound): 232 | collection = self.api.get_collection('asdf1234') 233 | with self.assertRaises(Forbidden): 234 | self.api = API('12345') 235 | collections = self.api.collection_search('apple') 236 | 237 | if __name__ == '__main__': 238 | unittest.main() 239 | --------------------------------------------------------------------------------