X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=diaspy%2Fstreams.py;h=b00139c371ea49d3c4b7d7f9a617536ff01fb83f;hb=d95ff94a76bbeeaee6fe63f5480c63c2d1d18b8f;hp=c9ca429a903827c26a12052a33a46af4508d5170;hpb=0c28bf0b27aff3d23f6308e0936f765e0582b924;p=diaspy.git diff --git a/diaspy/streams.py b/diaspy/streams.py index c9ca429..b00139c 100644 --- a/diaspy/streams.py +++ b/diaspy/streams.py @@ -20,521 +20,521 @@ We need this to get a UTC timestamp from the latest loaded post in the stream, so we can use it for the more() function. """ try: - import dateutil.parser - def parse_utc_timestamp(date_str): - return round(dateutil.parser.parse(date_str).timestamp()) + import dateutil.parser + def parse_utc_timestamp(date_str): + return round(dateutil.parser.parse(date_str).timestamp()) except ImportError: - try: - from datetime import datetime - from pytz import timezone - def parse_utc_timestamp(date_str): - return round(datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone('UTC')).timestamp()) + try: + from datetime import datetime + from pytz import timezone + def parse_utc_timestamp(date_str): + return round(datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=timezone('UTC')).timestamp()) - except ImportError: - print("Please install either python-dateutil or python-pytz") - exit # TODO raise exception + except ImportError: + print("Please install either python-dateutil or python-pytz") + exit # TODO raise exception class Generic(): - """Object representing generic stream. - """ - _location = 'stream.json' - - def __init__(self, connection, location='', fetch=True): - """ - :param connection: Connection() object - :type connection: diaspy.connection.Connection - :param location: location of json (optional) - :type location: str - :param fetch: will call .fill() if true - :type fetch: bool - """ - self._connection = connection - if location: self._location = location - self.latest = None - self._stream = [] - # since epoch - self.max_time = int(time.mktime(time.gmtime())) - if fetch: self.fill() - - def __contains__(self, post): - """Returns True if stream contains given post. - """ - return post in self._stream - - def __iter__(self): - """Provides iterable interface for stream. - """ - return iter(self._stream) - - def __getitem__(self, n): - """Returns n-th item in Stream. - """ - return self._stream[n] - - def __len__(self): - """Returns length of the Stream. - """ - return len(self._stream) - - def _obtain(self, max_time=0, suppress=True): - """Obtains stream from pod. - - suppress:bool - suppress post-fetching errors (e.g. 404) - """ - params = {} - if max_time: - if self.latest == None: - self.latest = int(time.mktime(time.gmtime()) * 1000) - self.latest -= max_time - else: self.latest += 1 - params['max_time'] = max_time - params['_'] = self.latest - print("Diaspy _obtain.params: {}".format(params)) - request = self._connection.get(self._location, params=params) - if request.status_code != 200: - raise errors.StreamError('wrong status code: {0}'.format(request.status_code)) - posts = [] - latest_time = None # Used to get the created_at from the latest posts we received. - for post in request.json(): - try: - comments = False - if post['interactions']['comments_count'] > 3: comments = True - posts.append(Post(self._connection, id=post['id'], guid=post['guid'], fetch=False, comments=comments, post_data=post)) - if post['created_at']: latest_time = post['created_at'] - except errors.PostError: - if not suppress: - raise - if latest_time: - self.max_time = parse_utc_timestamp( latest_time ) - return posts - - def _expand(self, new_stream): - """Appends older posts to stream. - """ - guids = [post.guid for post in self._stream] - stream = self._stream - for post in new_stream: - if post.guid not in guids: - stream.append(post) - guids.append(post.guid) - self._stream = stream - - def _update(self, new_stream): - """Updates stream with new posts. - """ - guids = [post.guid for post in self._stream] - - stream = self._stream - for i in range(len(new_stream)): - if new_stream[-i].guid not in guids: - stream = [new_stream[-i]] + stream - guids.append(new_stream[-i].guid) - self._stream = stream - - def clear(self): - """Set stream to empty. - """ - self._stream = [] - - def purge(self): - """Removes all unexistent posts from stream. - """ - stream = [] - for post in self._stream: - deleted = False - try: - # error will tell us that the post has been deleted - post.update() - except Exception: - deleted = True - finally: - if not deleted: stream.append(post) - self._stream = stream - - def update(self): - """Updates stream with new posts. - """ - self._update(self._obtain()) - - def fill(self): - """Fills the stream with posts. - - **Notice:** this will create entirely new list of posts. - If you want to preseve posts already present in stream use update(). - """ - self._stream = self._obtain() - - def more(self, max_time=0, backtime=86400): - """Tries to download more (older posts) posts from Stream. - - TODO backtime isn't used anymore. - Diaspora reference: https://github.com/diaspora/diaspora/blob/26a9e50ef935628c800f9a21d345057556fa5c31/app/helpers/stream_helper.rb#L48 - - :param backtime: how many seconds substract each time (defaults to one day) - :type backtime: int - :param max_time: seconds since epoch (optional, diaspy'll figure everything on its own) - :type max_time: int - """ - - if not max_time: max_time = self.max_time - self.max_time = max_time - new_stream = self._obtain(max_time=max_time) - self._expand(new_stream) - - def full(self, backtime=86400, retry=42, callback=None): - """Fetches full stream - containing all posts. - WARNING: this is a **VERY** long running function. - Use callback parameter to access information about the stream during its - run. - - Default backtime is one day. But sometimes user might not have any activity for longer - period (in the beginning of my D* activity I was posting once a month or so). - The role of retry is to hadle such situations by trying to go further back in time. - If a post is found the counter is restored. - - Default retry is 42. If you don't know why go to the nearest library (or to the nearest - Piratebay mirror) and grab a copy of "A Hitchhiker's Guide to the Galaxy" and read the - book to find out. This will also increase your level of geekiness and you'll have a - great time reading the book. - - :param backtime: how many seconds to substract each time - :type backtime: int - :param retry: how many times the functin should look deeper than your last post - :type retry: int - :param callback: callable taking diaspy.streams.Generic as an argument - :returns: integer, lenght of the stream - """ - oldstream = self.copy() - self.more() - while len(oldstream) < len(self): - oldstream = self.copy() - if callback is not None: callback(self) - self.more(backtime=backtime) - if len(oldstream) < len(self): continue - # but if no posts were found start retrying... - print('retrying... {0}'.format(retry)) - n = retry - while n > 0: - print('\t', n, self.max_time) - # try to get even more posts... - self.more(backtime=backtime) - print('\t', len(oldstream), len(self)) - # check if it was a success... - if len(oldstream) < len(self): - # and if so restore normal order of execution by - # going one loop higher - break - oldstream = self.copy() - # if it was not a success substract one backtime, keep calm and - # try going further back in time... - n -= 1 - # check the comment below - # no commented code should be present in good software - #if len(oldstream) == len(self): break - return len(self) - - def copy(self): - """Returns copy (list of posts) of current stream. - """ - return [p for p in self._stream] - - def json(self, comments=False, **kwargs): - """Returns JSON encoded string containing stream's data. - - :param comments: to include comments or not to include 'em, that is the question this param holds answer to - :type comments: bool - """ - stream = [post for post in self._stream] - if comments: - for i, post in enumerate(stream): - post._fetchcomments() - comments = [c.data for c in post.comments] - post['interactions']['comments'] = comments - stream[i] = post - stream = [post._data for post in stream] - return json.dumps(stream, **kwargs) + """Object representing generic stream. + """ + _location = 'stream.json' + + def __init__(self, connection, location='', fetch=True): + """ + :param connection: Connection() object + :type connection: diaspy.connection.Connection + :param location: location of json (optional) + :type location: str + :param fetch: will call .fill() if true + :type fetch: bool + """ + self._connection = connection + if location: self._location = location + self.latest = None + self._stream = [] + # since epoch + self.max_time = int(time.mktime(time.gmtime())) + if fetch: self.fill() + + def __contains__(self, post): + """Returns True if stream contains given post. + """ + return post in self._stream + + def __iter__(self): + """Provides iterable interface for stream. + """ + return iter(self._stream) + + def __getitem__(self, n): + """Returns n-th item in Stream. + """ + return self._stream[n] + + def __len__(self): + """Returns length of the Stream. + """ + return len(self._stream) + + def _obtain(self, max_time=0, suppress=True): + """Obtains stream from pod. + + suppress:bool - suppress post-fetching errors (e.g. 404) + """ + params = {} + if max_time: + if self.latest == None: + self.latest = int(time.mktime(time.gmtime()) * 1000) + self.latest -= max_time + else: self.latest += 1 + params['max_time'] = max_time + params['_'] = self.latest + print("Diaspy _obtain.params: {}".format(params)) + request = self._connection.get(self._location, params=params) + if request.status_code != 200: + raise errors.StreamError('wrong status code: {0}'.format(request.status_code)) + posts = [] + latest_time = None # Used to get the created_at from the latest posts we received. + for post in request.json(): + try: + comments = False + if post['interactions']['comments_count'] > 3: comments = True + posts.append(Post(self._connection, id=post['id'], guid=post['guid'], fetch=False, comments=comments, post_data=post)) + if post['created_at']: latest_time = post['created_at'] + except errors.PostError: + if not suppress: + raise + if latest_time: + self.max_time = parse_utc_timestamp( latest_time ) + return posts + + def _expand(self, new_stream): + """Appends older posts to stream. + """ + guids = [post.guid for post in self._stream] + stream = self._stream + for post in new_stream: + if post.guid not in guids: + stream.append(post) + guids.append(post.guid) + self._stream = stream + + def _update(self, new_stream): + """Updates stream with new posts. + """ + guids = [post.guid for post in self._stream] + + stream = self._stream + for i in range(len(new_stream)): + if new_stream[-i].guid not in guids: + stream = [new_stream[-i]] + stream + guids.append(new_stream[-i].guid) + self._stream = stream + + def clear(self): + """Set stream to empty. + """ + self._stream = [] + + def purge(self): + """Removes all unexistent posts from stream. + """ + stream = [] + for post in self._stream: + deleted = False + try: + # error will tell us that the post has been deleted + post.update() + except Exception: + deleted = True + finally: + if not deleted: stream.append(post) + self._stream = stream + + def update(self): + """Updates stream with new posts. + """ + self._update(self._obtain()) + + def fill(self): + """Fills the stream with posts. + + **Notice:** this will create entirely new list of posts. + If you want to preseve posts already present in stream use update(). + """ + self._stream = self._obtain() + + def more(self, max_time=0, backtime=86400): + """Tries to download more (older posts) posts from Stream. + + TODO backtime isn't used anymore. + Diaspora reference: https://github.com/diaspora/diaspora/blob/26a9e50ef935628c800f9a21d345057556fa5c31/app/helpers/stream_helper.rb#L48 + + :param backtime: how many seconds substract each time (defaults to one day) + :type backtime: int + :param max_time: seconds since epoch (optional, diaspy'll figure everything on its own) + :type max_time: int + """ + + if not max_time: max_time = self.max_time + self.max_time = max_time + new_stream = self._obtain(max_time=max_time) + self._expand(new_stream) + + def full(self, backtime=86400, retry=42, callback=None): + """Fetches full stream - containing all posts. + WARNING: this is a **VERY** long running function. + Use callback parameter to access information about the stream during its + run. + + Default backtime is one day. But sometimes user might not have any activity for longer + period (in the beginning of my D* activity I was posting once a month or so). + The role of retry is to hadle such situations by trying to go further back in time. + If a post is found the counter is restored. + + Default retry is 42. If you don't know why go to the nearest library (or to the nearest + Piratebay mirror) and grab a copy of "A Hitchhiker's Guide to the Galaxy" and read the + book to find out. This will also increase your level of geekiness and you'll have a + great time reading the book. + + :param backtime: how many seconds to substract each time + :type backtime: int + :param retry: how many times the functin should look deeper than your last post + :type retry: int + :param callback: callable taking diaspy.streams.Generic as an argument + :returns: integer, lenght of the stream + """ + oldstream = self.copy() + self.more() + while len(oldstream) < len(self): + oldstream = self.copy() + if callback is not None: callback(self) + self.more(backtime=backtime) + if len(oldstream) < len(self): continue + # but if no posts were found start retrying... + print('retrying... {0}'.format(retry)) + n = retry + while n > 0: + print('\t', n, self.max_time) + # try to get even more posts... + self.more(backtime=backtime) + print('\t', len(oldstream), len(self)) + # check if it was a success... + if len(oldstream) < len(self): + # and if so restore normal order of execution by + # going one loop higher + break + oldstream = self.copy() + # if it was not a success substract one backtime, keep calm and + # try going further back in time... + n -= 1 + # check the comment below + # no commented code should be present in good software + #if len(oldstream) == len(self): break + return len(self) + + def copy(self): + """Returns copy (list of posts) of current stream. + """ + return [p for p in self._stream] + + def json(self, comments=False, **kwargs): + """Returns JSON encoded string containing stream's data. + + :param comments: to include comments or not to include 'em, that is the question this param holds answer to + :type comments: bool + """ + stream = [post for post in self._stream] + if comments: + for i, post in enumerate(stream): + post._fetchcomments() + comments = [c.data for c in post.comments] + post['interactions']['comments'] = comments + stream[i] = post + stream = [post._data for post in stream] + return json.dumps(stream, **kwargs) class Outer(Generic): - """Object used by diaspy.models.User to represent - stream of other user. - """ - def __init__(self, connection, guid, fetch=True): - location = 'people/{}/stream.json'.format(guid) - super().__init__(connection, location, fetch) + """Object used by diaspy.models.User to represent + stream of other user. + """ + def __init__(self, connection, guid, fetch=True): + location = 'people/{}/stream.json'.format(guid) + super().__init__(connection, location, fetch) class Stream(Generic): - """The main stream containing the combined posts of the - followed users and tags and the community spotlights posts - if the user enabled those. - """ - location = 'stream.json' - - def post(self, text='', aspect_ids='public', photos=None, photo='', poll_question=None, poll_answers=None, location_coords=None, provider_display_name=''): - """This function sends a post to an aspect. - If both `photo` and `photos` are specified `photos` takes precedence. - - :param text: Text to post. - :type text: str - - :param aspect_ids: Aspect ids to send post to. - :type aspect_ids: str - - :param photo: filename of photo to post - :type photo: str - - :param photos: id of photo to post (obtained from _photoupload()) - :type photos: int - - :param provider_display_name: name of provider displayed under the post - :type provider_display_name: str - - :param poll_question: Question string - :type poll_question: str - - :param poll_answers: Anwsers to the poll - :type poll_answers: list with strings - - :param location_coords: TODO - :type location_coords: TODO - - :returns: diaspy.models.Post -- the Post which has been created - """ - data = {} - data['aspect_ids'] = aspect_ids - data['status_message'] = {'text': text, 'provider_display_name': provider_display_name} - if photo: data['photos'] = self._photoupload(photo) - if photos: data['photos'] = photos - if poll_question and poll_answers: - data['poll_question'] = poll_question - data['poll_answers'] = poll_answers - if location_coords: data['location_coords'] = location_coords - - request = self._connection.post('status_messages', - data=json.dumps(data), - headers={'content-type': 'application/json', - 'accept': 'application/json', - 'x-csrf-token': repr(self._connection)}) - if request.status_code != 201: - raise Exception('{0}: Post could not be posted.'.format(request.status_code)) - post_json = request.json() - post = Post(self._connection, id=post_json['id'], guid=post_json['guid'], post_data=post_json) - return post - - def _photoupload(self, filename, aspects=[]): - """Uploads picture to the pod. - - :param filename: path to picture file - :type filename: str - :param aspect_ids: list of ids of aspects to which you want to upload this photo - :type aspect_ids: list of integers - - :returns: id of the photo being uploaded - """ - data = open(filename, 'rb') - image = data.read() - data.close() - - params = {} - params['photo[pending]'] = 'true' - params['set_profile_image'] = '' - params['qqfile'] = filename - if not aspects: aspects = self._connection.getUserData()['aspects'] - for i, aspect in enumerate(aspects): - params['photo[aspect_ids][{0}]'.format(i)] = aspect['id'] - - headers = {'content-type': 'application/octet-stream', - 'x-csrf-token': repr(self._connection), - 'x-file-name': filename} - - request = self._connection.post('photos', data=image, params=params, headers=headers) - if request.status_code != 200: - raise errors.StreamError('photo cannot be uploaded: {0}'.format(request.status_code)) - return request.json()['data']['photo']['id'] + """The main stream containing the combined posts of the + followed users and tags and the community spotlights posts + if the user enabled those. + """ + location = 'stream.json' + + def post(self, text='', aspect_ids='public', photos=None, photo='', poll_question=None, poll_answers=None, location_coords=None, provider_display_name=''): + """This function sends a post to an aspect. + If both `photo` and `photos` are specified `photos` takes precedence. + + :param text: Text to post. + :type text: str + + :param aspect_ids: Aspect ids to send post to. + :type aspect_ids: str + + :param photo: filename of photo to post + :type photo: str + + :param photos: id of photo to post (obtained from _photoupload()) + :type photos: int + + :param provider_display_name: name of provider displayed under the post + :type provider_display_name: str + + :param poll_question: Question string + :type poll_question: str + + :param poll_answers: Anwsers to the poll + :type poll_answers: list with strings + + :param location_coords: TODO + :type location_coords: TODO + + :returns: diaspy.models.Post -- the Post which has been created + """ + data = {} + data['aspect_ids'] = aspect_ids + data['status_message'] = {'text': text, 'provider_display_name': provider_display_name} + if photo: data['photos'] = self._photoupload(photo) + if photos: data['photos'] = photos + if poll_question and poll_answers: + data['poll_question'] = poll_question + data['poll_answers'] = poll_answers + if location_coords: data['location_coords'] = location_coords + + request = self._connection.post('status_messages', + data=json.dumps(data), + headers={'content-type': 'application/json', + 'accept': 'application/json', + 'x-csrf-token': repr(self._connection)}) + if request.status_code != 201: + raise Exception('{0}: Post could not be posted.'.format(request.status_code)) + post_json = request.json() + post = Post(self._connection, id=post_json['id'], guid=post_json['guid'], post_data=post_json) + return post + + def _photoupload(self, filename, aspects=[]): + """Uploads picture to the pod. + + :param filename: path to picture file + :type filename: str + :param aspect_ids: list of ids of aspects to which you want to upload this photo + :type aspect_ids: list of integers + + :returns: id of the photo being uploaded + """ + data = open(filename, 'rb') + image = data.read() + data.close() + + params = {} + params['photo[pending]'] = 'true' + params['set_profile_image'] = '' + params['qqfile'] = filename + if not aspects: aspects = self._connection.getUserData()['aspects'] + for i, aspect in enumerate(aspects): + params['photo[aspect_ids][{0}]'.format(i)] = aspect['id'] + + headers = {'content-type': 'application/octet-stream', + 'x-csrf-token': repr(self._connection), + 'x-file-name': filename} + + request = self._connection.post('photos', data=image, params=params, headers=headers) + if request.status_code != 200: + raise errors.StreamError('photo cannot be uploaded: {0}'.format(request.status_code)) + return request.json()['data']['photo']['id'] class Activity(Stream): - """Stream representing user's activity. - """ - _location = 'activity.json' - - def _delid(self, id): - """Deletes post with given id. - """ - post = None - for p in self._stream: - if p['id'] == id: - post = p - break - if post is not None: post.delete() - - def delete(self, post): - """Deletes post from users activity. - `post` can be either post id or Post() - object which will be identified and deleted. - After deleting post the stream will be purged. - - :param post: post identifier - :type post: str, diaspy.models.Post - """ - if type(post) == str: self._delid(post) - elif type(post) == Post: post.delete() - else: raise TypeError('this method accepts str or Post types: {0} given') - self.purge() + """Stream representing user's activity. + """ + _location = 'activity.json' + + def _delid(self, id): + """Deletes post with given id. + """ + post = None + for p in self._stream: + if p['id'] == id: + post = p + break + if post is not None: post.delete() + + def delete(self, post): + """Deletes post from users activity. + `post` can be either post id or Post() + object which will be identified and deleted. + After deleting post the stream will be purged. + + :param post: post identifier + :type post: str, diaspy.models.Post + """ + if type(post) == str: self._delid(post) + elif type(post) == Post: post.delete() + else: raise TypeError('this method accepts str or Post types: {0} given') + self.purge() class Aspects(Generic): - """This stream contains the posts filtered by - the specified aspect IDs. You can choose the aspect IDs with - the parameter `aspect_ids` which value should be - a comma seperated list of aspect IDs. - If the parameter is ommitted all aspects are assumed. - An example call would be `aspects.json?aspect_ids=23,5,42` - """ - _location = 'aspects.json' - - def getAspectID(self, aspect_name): - """Returns id of an aspect of given name. - Returns -1 if aspect is not found. - - :param aspect_name: aspect name (must be spelled exactly as when created) - :type aspect_name: str - :returns: int - """ - id = -1 - aspects = self._connection.getUserData()['aspects'] - for aspect in aspects: - if aspect['name'] == aspect_name: id = aspect['id'] - return id - - def filter(self, ids): - """Filters posts by given aspect ids. - - :parameter ids: list of apsect ids - :type ids: list of integers - """ - self._location = 'aspects.json?a_ids[]=' + '{}'.format('&a_ids[]='.join(ids)) - self.fill() # this will create entirely new list of posts. - - def add(self, aspect_name, visible=0): - """This function adds a new aspect. - Status code 422 is accepted because it is returned by D* when - you try to add aspect already present on your aspect list. - - :param aspect_name: name of aspect to create - :param visible: whether the contacts in this aspect are visible to each other or not - - :returns: Aspect() object of just created aspect - """ - data = {'authenticity_token': repr(self._connection), - 'aspect[name]': aspect_name, - 'aspect[contacts_visible]': visible} - - request = self._connection.post('aspects', data=data) - if request.status_code not in [200, 422]: - raise Exception('wrong status code: {0}'.format(request.status_code)) - - id = self.getAspectID(aspect_name) - return Aspect(self._connection, id) - - def remove(self, id=-1, name=''): - """This method removes an aspect. - You can give it either id or name of the aspect. - When both are specified, id takes precedence over name. - - Status code 500 is accepted because although the D* will - go nuts it will remove the aspect anyway. - - :param aspect_id: id fo aspect to remove - :type aspect_id: int - :param name: name of aspect to remove - :type name: str - """ - if id == -1 and name: id = self.getAspectID(name) - data = {'_method': 'delete', - 'authenticity_token': repr(self._connection)} - request = self._connection.post('aspects/{0}'.format(id), data=data) - if request.status_code not in [200, 302, 500]: - raise Exception('wrong status code: {0}: cannot remove aspect'.format(request.status_code)) + """This stream contains the posts filtered by + the specified aspect IDs. You can choose the aspect IDs with + the parameter `aspect_ids` which value should be + a comma seperated list of aspect IDs. + If the parameter is ommitted all aspects are assumed. + An example call would be `aspects.json?aspect_ids=23,5,42` + """ + _location = 'aspects.json' + + def getAspectID(self, aspect_name): + """Returns id of an aspect of given name. + Returns -1 if aspect is not found. + + :param aspect_name: aspect name (must be spelled exactly as when created) + :type aspect_name: str + :returns: int + """ + id = -1 + aspects = self._connection.getUserData()['aspects'] + for aspect in aspects: + if aspect['name'] == aspect_name: id = aspect['id'] + return id + + def filter(self, ids): + """Filters posts by given aspect ids. + + :parameter ids: list of apsect ids + :type ids: list of integers + """ + self._location = 'aspects.json?a_ids[]=' + '{}'.format('&a_ids[]='.join(ids)) + self.fill() # this will create entirely new list of posts. + + def add(self, aspect_name, visible=0): + """This function adds a new aspect. + Status code 422 is accepted because it is returned by D* when + you try to add aspect already present on your aspect list. + + :param aspect_name: name of aspect to create + :param visible: whether the contacts in this aspect are visible to each other or not + + :returns: Aspect() object of just created aspect + """ + data = {'authenticity_token': repr(self._connection), + 'aspect[name]': aspect_name, + 'aspect[contacts_visible]': visible} + + request = self._connection.post('aspects', data=data) + if request.status_code not in [200, 422]: + raise Exception('wrong status code: {0}'.format(request.status_code)) + + id = self.getAspectID(aspect_name) + return Aspect(self._connection, id) + + def remove(self, id=-1, name=''): + """This method removes an aspect. + You can give it either id or name of the aspect. + When both are specified, id takes precedence over name. + + Status code 500 is accepted because although the D* will + go nuts it will remove the aspect anyway. + + :param aspect_id: id fo aspect to remove + :type aspect_id: int + :param name: name of aspect to remove + :type name: str + """ + if id == -1 and name: id = self.getAspectID(name) + data = {'_method': 'delete', + 'authenticity_token': repr(self._connection)} + request = self._connection.post('aspects/{0}'.format(id), data=data) + if request.status_code not in [200, 302, 500]: + raise Exception('wrong status code: {0}: cannot remove aspect'.format(request.status_code)) class Commented(Generic): - """This stream contains all posts - the user has made a comment on. - """ - _location = 'commented.json' + """This stream contains all posts + the user has made a comment on. + """ + _location = 'commented.json' class Liked(Generic): - """This stream contains all posts the user liked. - """ - _location = 'liked.json' + """This stream contains all posts the user liked. + """ + _location = 'liked.json' class Mentions(Generic): - """This stream contains all posts - the user is mentioned in. - """ - _location = 'mentions.json' + """This stream contains all posts + the user is mentioned in. + """ + _location = 'mentions.json' class FollowedTags(Generic): - """This stream contains all posts - containing tags the user is following. - """ - _location = 'followed_tags.json' - - def get(self): - """Returns list of followed tags. - """ - return [] - - def remove(self, tag_id): - """Stop following a tag. - - :param tag_id: tag id - :type tag_id: int - """ - data = {'authenticity_token': self._connection.get_token()} - request = self._connection.delete('tag_followings/{0}'.format(tag_id), data=data) - if request.status_code != 404: - raise Exception('wrong status code: {0}'.format(request.status_code)) - - def add(self, tag_name): - """Follow new tag. - Error code 403 is accepted because pods respod with it when request - is sent to follow a tag that a user already follows. - - :param tag_name: tag name - :type tag_name: str - :returns: int (response code) - """ - data = {'name': tag_name, - 'authenticity_token': repr(self._connection), - } - headers = {'content-type': 'application/json', - 'x-csrf-token': repr(self._connection), - 'accept': 'application/json' - } - - request = self._connection.post('tag_followings', data=json.dumps(data), headers=headers) - - if request.status_code not in [201, 403]: - raise Exception('wrong error code: {0}'.format(request.status_code)) - return request.status_code + """This stream contains all posts + containing tags the user is following. + """ + _location = 'followed_tags.json' + + def get(self): + """Returns list of followed tags. + """ + return [] + + def remove(self, tag_id): + """Stop following a tag. + + :param tag_id: tag id + :type tag_id: int + """ + data = {'authenticity_token': self._connection.get_token()} + request = self._connection.delete('tag_followings/{0}'.format(tag_id), data=data) + if request.status_code != 404: + raise Exception('wrong status code: {0}'.format(request.status_code)) + + def add(self, tag_name): + """Follow new tag. + Error code 403 is accepted because pods respod with it when request + is sent to follow a tag that a user already follows. + + :param tag_name: tag name + :type tag_name: str + :returns: int (response code) + """ + data = {'name': tag_name, + 'authenticity_token': repr(self._connection), + } + headers = {'content-type': 'application/json', + 'x-csrf-token': repr(self._connection), + 'accept': 'application/json' + } + + request = self._connection.post('tag_followings', data=json.dumps(data), headers=headers) + + if request.status_code not in [201, 403]: + raise Exception('wrong error code: {0}'.format(request.status_code)) + return request.status_code class Tag(Generic): - """This stream contains all posts containing a tag. - """ - def __init__(self, connection, tag, fetch=True): - """ - :param connection: Connection() object - :type connection: diaspy.connection.Connection - :param tag: tag name - :type tag: str - """ - self._connection = connection - self._location = 'tags/{0}.json'.format(tag) - if fetch: self.fill() + """This stream contains all posts containing a tag. + """ + def __init__(self, connection, tag, fetch=True): + """ + :param connection: Connection() object + :type connection: diaspy.connection.Connection + :param tag: tag name + :type tag: str + """ + self._connection = connection + self._location = 'tags/{0}.json'.format(tag) + if fetch: self.fill()