def create(cls, host, data, access_token, headers=None): """ Create an API resource with POST. """ resource_url = build_url(host, [cls.resource_fragment]) params = {'access_token': access_token} if headers: headers['Content-Type'] = 'application/json' headers['Host'] = host else: headers = {'Content-Type': 'application/json', 'Host': host} response = requests.post(resource_url, data=json.dumps(data, cls=DateTimeEncoder), headers=headers, params=params) try: resource = response.json() except ValueError: raise APIException( 'The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code) if not resource['data']: raise APIException('No data returned at: %s' % resource_url) return resource['data']
def retrieve(cls, host, q, access_token): """ Forward a geocode request (q) to the API. """ params = {'q': q} headers = {'Authorization': 'Bearer %s' % access_token} response = requests.get(build_url(host, ['geocode']), params=params, headers=headers) return response.content
def rsvp(cls, host, event_id, profile_id, attendance_data, access_token): """ Create or update attendance to an event. TODO: This is obviously pretty nasty but it'll be changed soon. """ collection_url = build_url( host, [cls.resource_fragment, event_id, 'attendees']) item_url = collection_url + '/' + str(profile_id) # See if there is an attendance entry for this profile try: response = requests.get(item_url, params={'access_token': access_token}) except RequestException: raise # If it is not found, POST an attendance if response.status_code == 404: try: print 'Not found, posting to ' + collection_url post_response = requests.post( collection_url, attendance_data, params={'access_token': access_token}) except RequestException: raise try: post_response.json() except ValueError: raise APIException('Invalid JSON returned') return # Attendance record exists, so update it with PUT elif response.status_code >= 200 and response.status_code < 400: try: print 'Found, putting to ' + item_url put_response = requests.post(item_url, data=attendance_data, params={ 'method': 'PUT', 'access_token': access_token }) except RequestException: raise try: put_response.json() except ValueError: raise APIException('Invalid JSON returned') return else: raise APIException(response.content)
def process_response(self, request, response): """Deletes the user's access token cookie if it has previously been marked as invalid (by process_request) """ if hasattr(request, 'delete_token') and request.delete_token: response.delete_cookie('access_token') requests.delete(build_url(request.META['HTTP_HOST'], ['auth', request.COOKIES['access_token']])) return response
def retrieve_attendees(cls, host, id, access_token=None): """ Retrieve a list of attendees for an event. TODO: pagination support """ resource_url = build_url(host, [cls.resource_fragment, id, 'attendees']) resource = APIResource.retrieve(host, id=id, access_token=access_token, url_override=resource_url) resource = cls.create_linkmap(resource) return APIResource.process_timestamp(resource)
def process_response(self, request, response): """Deletes the user's access token cookie if it has previously been marked as invalid (by process_request) """ if hasattr(request, 'delete_token') and request.delete_token: response.delete_cookie('access_token') requests.delete( build_url(request.META['HTTP_HOST'], ['auth', request.COOKIES['access_token']])) return response
def rsvp(cls, host, event_id, profile_id, attendance_data, access_token): """ Create or update attendance to an event. TODO: This is obviously pretty nasty but it'll be changed soon. """ collection_url = build_url(host, [cls.resource_fragment, event_id, 'attendees']) item_url = collection_url + '/' + str(profile_id) # See if there is an attendance entry for this profile try: response = requests.get(item_url, params={'access_token': access_token}) except RequestException: raise # If it is not found, POST an attendance if response.status_code == 404: try: print 'Not found, posting to ' + collection_url post_response = requests.post(collection_url, attendance_data, params={'access_token': access_token}) except RequestException: raise try: post_response.json() except ValueError: raise APIException('Invalid JSON returned') return # Attendance record exists, so update it with PUT elif response.status_code >= 200 and response.status_code < 400: try: print 'Found, putting to ' + item_url put_response = requests.post( item_url, data=attendance_data, params={'method': 'PUT', 'access_token': access_token} ) except RequestException: raise try: put_response.json() except ValueError: raise APIException('Invalid JSON returned') return else: raise APIException(response.content)
def retrieve(cls, host, id=None, offset=None, access_token=None, url_override=None): """ GET an API resource. If resource ID is omitted, returns a list. Appends access_token and offset (for paging) if provided. """ headers = {'Host': host} if url_override: resource_url = url_override else: path_fragments = [cls.resource_fragment] if id: id = int(id) assert id > 0, 'Resource ID must be greater than zero' path_fragments.append(id) resource_url = build_url(host, path_fragments) params = {} if access_token: params['access_token'] = access_token if offset: offset = int(offset) assert offset % 25 == 0, 'Offset must be a multiple of 25' params['offset'] = offset response = requests.get(resource_url, params=params, headers=headers) try: resource = response.json() except ValueError: raise APIException( 'The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code) if not resource['data']: raise APIException('No data returned at: %s' % resource_url) return resource['data']
def logout(request): """ Log a user out. Issues a DELETE request to the backend for the user's access_token, and issues a delete cookie header in response to clear the user's access_token cookie. """ view_data = { 'site': request.site, } response = render(request, 'logout.html', view_data) if request.COOKIES.has_key('access_token'): response.delete_cookie('access_token') url = build_url(request.META['HTTP_HOST'], ['auth',request.access_token]) requests.post(url, params={'method': 'DELETE', 'access_token': request.access_token}) return response
def testMicrocosmsView(self): # Construct a random subdomain string subdomain = '' for x in xrange(10): subdomain += random.choice(string.lowercase) host = '%s.microco.sm' % subdomain # Create a request for a list of microcosms request = self.factory.get('/microcosms', HTTP_HOST=host) request.access_token = None request.whoami = None request.site = None # Patch requests.get and check the call args with patch('requests.get') as mock: mock.return_value.json.return_value = {'error': None, 'data': {'hi': 5}} MicrocosmView.list(request) path = build_url(host, ['microcosms']) mock.assert_called_once_with(path, headers={'Host': host}, params={})
def retrieve(cls, host, id=None, offset=None, access_token=None, url_override=None): """ GET an API resource. If resource ID is omitted, returns a list. Appends access_token and offset (for paging) if provided. """ headers = {'Host': host} if url_override: resource_url = url_override else: path_fragments = [cls.resource_fragment] if id: id = int(id) assert id > 0, 'Resource ID must be greater than zero' path_fragments.append(id) resource_url = build_url(host, path_fragments) params = {} if access_token: params['access_token'] = access_token if offset: offset = int(offset) assert offset % 25 == 0, 'Offset must be a multiple of 25' params['offset'] = offset response = requests.get(resource_url, params=params, headers=headers) try: resource = response.json() except ValueError: raise APIException('The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code) if not resource['data']: raise APIException('No data returned at: %s' % resource_url) return resource['data']
def delete(cls, host, id, access_token): """ DELETE an API resource. ID must be supplied. A 'data' object is never returned by a DELETE, so this method will raise an exception on failure. In normal operation the method simply returns. """ path_fragments = [cls.resource_fragment] if id: id = int(id) assert id > 0, 'Resource ID must be greater than zero' path_fragments.append(id) elif access_token: path_fragments.append(access_token) else: raise AssertionError, 'You must supply either an id or '\ 'an access_token to delete' resource_url = build_url(host, path_fragments) params = { 'method': 'DELETE', 'access_token': access_token, } headers = {'Host': host} response = requests.post(resource_url, params=params, headers=headers) try: resource = response.json() except ValueError: raise APIException( 'The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code)
def update(cls, host, data, id, access_token): """ Update an API resource with PUT. """ id = int(id) assert id > 0, 'Resource ID must be greater than zero' path_fragments = [cls.resource_fragment, id] resource_url = build_url(host, path_fragments) headers = { 'Content-Type': 'application/json', 'Host': host, } params = { 'method': 'PUT', 'access_token': access_token, } response = requests.post( resource_url, data=json.dumps(data, cls=DateTimeEncoder), headers=headers, params=params ) try: resource = response.json() except ValueError: raise APIException('The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code) if not resource['data']: raise APIException('No data returned at: %s' % resource_url) return resource['data']
def update(cls, host, data, id, access_token): """ Update an API resource with PUT. """ id = int(id) assert id > 0, 'Resource ID must be greater than zero' path_fragments = [cls.resource_fragment, id] resource_url = build_url(host, path_fragments) headers = { 'Content-Type': 'application/json', 'Host': host, } params = { 'method': 'PUT', 'access_token': access_token, } response = requests.post(resource_url, data=json.dumps(data, cls=DateTimeEncoder), headers=headers, params=params) try: resource = response.json() except ValueError: raise APIException( 'The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code) if not resource['data']: raise APIException('No data returned at: %s' % resource_url) return resource['data']
def login(request): """ Log a user in. Creates an access_token using a persona assertion and the client secret. Sets this access token as a cookie. 'target_url' based as a GET parameter determines where the user is redirected. """ target_url = request.POST.get('target_url') assertion = request.POST.get('Assertion') client_secret = settings.CLIENT_SECRET data = { "Assertion": assertion, "ClientSecret": client_secret } headers= {'Host': request.META.get('HTTP_HOST')} access_token = requests.post(build_url(request.META['HTTP_HOST'], ['auth']), data=data, headers=headers).json()['data'] response = HttpResponseRedirect(target_url if target_url != '' else '/') response.set_cookie('access_token', access_token, httponly=True) return response
def logout(request): """ Log a user out. Issues a DELETE request to the backend for the user's access_token, and issues a delete cookie header in response to clear the user's access_token cookie. """ view_data = { 'site': request.site, } response = render(request, 'logout.html', view_data) if request.COOKIES.has_key('access_token'): response.delete_cookie('access_token') url = build_url(request.META['HTTP_HOST'], ['auth', request.access_token]) requests.post(url, params={ 'method': 'DELETE', 'access_token': request.access_token }) return response
def login(request): """ Log a user in. Creates an access_token using a persona assertion and the client secret. Sets this access token as a cookie. 'target_url' based as a GET parameter determines where the user is redirected. """ target_url = request.POST.get('target_url') assertion = request.POST.get('Assertion') client_secret = settings.CLIENT_SECRET data = {"Assertion": assertion, "ClientSecret": client_secret} headers = {'Host': request.META.get('HTTP_HOST')} access_token = requests.post(build_url(request.META['HTTP_HOST'], ['auth']), data=data, headers=headers).json()['data'] response = HttpResponseRedirect( target_url if target_url != '' else '/') response.set_cookie('access_token', access_token, httponly=True) return response
def delete(cls, host, id, access_token): """ DELETE an API resource. ID must be supplied. A 'data' object is never returned by a DELETE, so this method will raise an exception on failure. In normal operation the method simply returns. """ path_fragments = [cls.resource_fragment] if id: id = int(id) assert id > 0, 'Resource ID must be greater than zero' path_fragments.append(id) elif access_token: path_fragments.append(access_token) else: raise AssertionError, 'You must supply either an id or '\ 'an access_token to delete' resource_url = build_url(host, path_fragments) params = { 'method': 'DELETE', 'access_token': access_token, } headers = {'Host': host} response = requests.post(resource_url, params=params, headers=headers) try: resource = response.json() except ValueError: raise APIException('The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code)
def create(cls, host, data, access_token, headers=None): """ Create an API resource with POST. """ resource_url = build_url(host, [cls.resource_fragment]) params = {'access_token': access_token} if headers: headers['Content-Type'] = 'application/json' headers['Host'] = host else: headers = { 'Content-Type': 'application/json', 'Host': host } response = requests.post( resource_url, data=json.dumps(data, cls=DateTimeEncoder), headers=headers, params=params ) try: resource = response.json() except ValueError: raise APIException('The API has returned invalid json: %s' % response.content, 500) if resource['error']: raise APIException(resource['error'], response.status_code) if not resource['data']: raise APIException('No data returned at: %s' % resource_url) return resource['data']
def testFailCustomDomains(self): with self.assertRaises(AssertionError): build_url('a.example.org', ['resource', '1', 'ex/tra'])
def testInvalidFragment(self): with self.assertRaises(AssertionError): build_url('a.microco.sm', ['resource', '1', 'ex/tra'])
def testIntFragment(self): url = build_url('a.microco.sm', [1, 2, 3]) assert url == 'https://a.microco.sm/api/v1/1/2/3'
def testEmptyFragments(self): url = build_url('a.microco.sm', []) assert url == 'https://a.microco.sm/api/v1'
def testWithNoSeparator(self): url = build_url('a.microco.sm', ['resource', '1', 'extra']) assert url == 'https://a.microco.sm/api/v1/resource/1/extra'
def testWithDuplicateSeparator(self): url = build_url('a.microco.sm', ['resource/', '/1/', '/extra/']) assert url == 'https://a.microco.sm/api/v1/resource/1/extra'
def testWithPrependedSeparator(self): url = build_url('a.microco.sm', ['/resource', '/1', '/extra']) assert url == 'https://a.microco.sm/api/v1/resource/1/extra'