And the test suite was updated. Yay!
+
+**`0.4.1-rc.3` (2013-09-08):**
+
+* __new__: `diaspy.settings.Profile.load()` method for loading profile information,
+* __new__: `diaspy.settings.Profile.update()` method for updating profile information,
+* __new__: `diaspy.settings.Profile.setName()` method,
+* __new__: `diaspy.settings.Profile.setBio()` method,
+* __new__: `diaspy.settings.Profile.setLocation()` method,
+* __new__: `diaspy.settings.Profile.setTags()` method,
+* __new__: `diaspy.settings.Profile.setGender()` method,
+* __new__: `diaspy.settings.Profile.setBirthDate()` method,
+* __new__: `diaspy.settings.Profile.setSearchable()` method,
+* __new__: `diaspy.settings.Profile.setNSFW()` method,
+
+
**`0.4.1-rc.2` (2013-09-06):**
* __new__: `diaspy.search.Search.tags()` method for getting tag suggestions,
* __new__: `diaspy.settings.Profile.isNSFW()` method,
* __new__: `provider_display_name` parameter in `diaspy.streams.Stream.post()` (thanks @svbergerem),
+
* __upd__: `remeber_me` parameter in `diaspy.connection.Connection.login()`,
* __upd__: you must supply `username` and `password` parameters on init of `diaspy.connection.Connection`,
* __upd__: you must update your testconf.py (new fields are required for settings tests),
* __upd__: `diaspy.settings.Settings` renamed to `diaspy.settings.Account`,
+
* __rem__: `username` and `password` parameters removed from `diaspy.connection.Connection.login()`
must be supplied on init,
* __new__: `getSessionToken()` method in `diaspy.connection.Connection` returns string from `_diaspora_session` cookie,
* __new__: `direct` parameter in `diaspy.connection.Connection().get()` allowing to disable pod expansion,
+
* __upd__: if `Post()` is created with fetched comments, data will also be fetched as a dependency,
* __upd__: `id` argument type is now `int` (`diaspy.models.Post.__init__()`),
* __upd__: `Search().lookup_user()` renamed to `Search().lookupUser()`,
* __upd__: `diaspy.connection.Connection.podswitch()` gained two new positional arguments: `username` and `password`,
* __upd__: `aspect_id` renamed to `id` in `diaspy.streams.Aspects().remove()`,
-* __fix__: fixed some bugs in regular expressions used by `diaspy` internals
- (html tag removal, so you get nicer notifications),
+
+* __fix__: fixed some bugs in regular expressions used by `diaspy` internals (html tag removal, so you get nicer notifications),
* __fix__: fixed authentication issues,
* __dep__: `diaspy.client` is officially deprecated (will be removed in `0.4.1`),
+
* __upd__: `diaspy.conversations` renamed to `diaspy.messages`,
* __udp__: `diaspy.conversations.Conversation` moved to `diaspy.models`,
+
* __new__: `diaspy.messages.Mailbox()` object representing diaspora\* mailbox,
----
-Version `0.3.2` (2013-08-20):
+#### Version `0.3.2` (2013-08-20):
* __upd__: `diaspy.connection.getUserData()` raises `DiaspyError` when it cannot find user data,
+
* __rem__: `diaspy.client.Client` must be explicitly imported,
----
-Version `0.3.1` (2013-07-12):
+#### Version `0.3.1` (2013-07-12):
* __upd__: `diaspy.people.sephandle()` raises `InvalidHandleError` instead of `UserError`
* __upd__: `models.Post()._fetch()` renamed to `_fetchdata()` (because of new `_fetchcomments()` method)
+
+
* __new__: `models.Comment()` object: wrapper for comments, not to be created manually
* __new__: `comments` parameter in `models.Post`: defines whether to fetch post's commets
* __new__: `connection.Connection` has new parameter in `__init__()`: it's `schema`
----
-Version `0.3.0` (2013-07-07):
+#### Version `0.3.0` (2013-07-07):
First edition of Changelog for `diaspy`.
Developers should update their code as version `0.3.0` may not be fully
Version `0.3.0` introduces few new features, fixes several bugs and brings a bit of
redesign and refactoring od `diaspy`'s code.
-----
+
from diaspy import errors, streams
+class Account():
+ """Provides interface to account settings.
+ """
+ def __init__(self, connection):
+ self._connection = connection
+
+ def downloadxml(self):
+ """Returns downloaded XML.
+ """
+ request = self._connection.get('user/export')
+ return request.text
+
+ def downloadPhotos(self, size='large', path='.', mark_nsfw=True, _critical=False, _stream=None):
+ """Downloads photos into the current working directory.
+ Sizes are: large, medium, small.
+ Filename is: {post_guid}_{photo_guid}.{extension}
+
+ Normally, this method will catch urllib-generated errors and
+ just issue warnings about photos that couldn't be downloaded.
+ However, with _critical param set to True errors will become
+ critical - the will be reraised in finally block.
+
+ :param size: size of the photos to download - large, medium or small
+ :type size: str
+ :param path: path to download (defaults to current working directory
+ :type path: str
+ :param mark_nsfw: will append '-nsfw' to images from posts marked as nsfw,
+ :type mark_nsfw: bool
+ :param _stream: diaspy.streams.Generic-like object (only for testing)
+ :param _critical: if True urllib errors will be reraised after generating a warning (may be removed)
+
+ :returns: integer, number of photos downloaded
+ """
+ photos = 0
+ if _stream is None:
+ stream = streams.Activity(self._connection)
+ stream.full()
+ else:
+ stream = _stream
+ for i, post in enumerate(stream):
+ if post['nsfw'] is not False: nsfw = '-nsfw'
+ else: nsfw = ''
+ if post['photos']:
+ for n, photo in enumerate(post['photos']):
+ name = '{0}_{1}{2}.{3}'.format(post['guid'], photo['guid'], nsfw, photo['sizes'][size].split('.')[-1])
+ filename = os.path.join(path, name)
+ try:
+ urllib.request.urlretrieve(url=photo['sizes'][size], filename=filename)
+ except (urllib.error.HTTPError, urllib.error.URLError) as e:
+ warnings.warn('downloading image {0} from post {1}: {2}'.format(photo['guid'], post['guid'], e))
+ finally:
+ if _critical: raise
+ photos += 1
+ return photos
+
+ def setEmail(self, email):
+ """Changes user's email.
+ """
+ data = {'_method': 'put', 'utf8': '✓', 'user[email]': email, 'authenticity_token': repr(self._connection)}
+ request = self._connection.post('user', data=data, allow_redirects=False)
+
+ def getEmail(self):
+ """Returns currently used email.
+ """
+ data = self._connection.get('user/edit')
+ email = re.compile('<input id="user_email" name="user\[email\]" size="30" type="text" value=".+?"').search(data.text)
+ if email is None: raise errors.DiaspyError('cannot fetch email')
+ email = email.group(0)[:-1]
+ email = email[email.rfind('"')+1:]
+ return email
+
+ def setLanguage(self, lang):
+ """Changes user's email.
+
+ :param lang: language identifier from getLanguages()
+ """
+ data = {'_method': 'put', 'utf8': '✓', 'user[language]': lang, 'authenticity_token': repr(self._connection)}
+ request = self._connection.post('user', data=data, allow_redirects=False)
+ return request.status_code
+
+ def getLanguages(self):
+ """Returns a list of tuples containing ('Language name', 'identifier').
+ One of the Black Magic(tm) methods.
+ """
+ selection_start = '<select id="user_language" name="user[language]">'
+ selection_end = '</select>'
+ languages = []
+ request = self._connection.get('user/edit')
+ data = request.text[request.text.find(selection_start)+len(selection_start):]
+ data = data[:data.find(selection_end)].split('\n')
+ for item in data:
+ name = item[item.find('>')+1:item.rfind('<')]
+ identifier = item[item.find('"')+1:]
+ identifier = identifier[:identifier.find('"')]
+ languages.append((name, identifier))
+ return languages
+
+
+class Privacy():
+ """Provides interface to provacy settings.
+ """
+ def __init__(self, connection):
+ self._connection = connection
+
+
class Profile():
- """Provides profile editing methods.
+ """Provides interface to profile settigns.
+
+ WARNING:
+
+ Because of the way update requests for profile are created every field must be sent.
+ The `load()` method is used to load all information into the dictionary.
+ Setters can then be used to adjust the data.
+ Finally, `update()` can be called to send data back to pod.
"""
firstname_regexp = re.compile('<input id="profile_first_name" name="profile\[first_name\]" type="text" value="(.*?)" />')
lastname_regexp = re.compile('<input id="profile_last_name" name="profile\[last_name\]" type="text" value="(.*?)" />')
"""
return self._connection.get('profile/edit').text
- def load(self):
- """Loads profile data into self.data dictionary.
- **Notice:** Not all keys are loaded yet.
- """
- self.data['profile[first_name]'], self.data['profile[last_name]'] = self.getName()
- self.data['profile[bio]'] = self.getBio()
- self.data['profile[location]'] = self.getLocation()
- self.data['profile[gender]'] = self.getGender()
- year, month, day = self.getBirthDate(named_month=False)
- self.data['profile[date][]'] = year
- self.data['profile[date][]'] = month
- self.data['profile[date][]'] = day
- self._loaded = True
-
- def update(self):
- """Updates profile information.
- """
- if not self._loaded: raise errors.DiaspyError('profile was not loaded')
- data['authenticity_token'] = repr(self._connection)
- request = self._connection.post('profile', data=json.dumps(data), allow_redirects=False)
- return request.status_code
-
def getName(self):
"""Returns two-tuple: (first, last) name.
"""
else: nsfw = True
return nsfw
+ def setName(self, first, last):
+ """Set first and last name.
+ """
+ self.data['profile[first_name]'] = first
+ self.data['profile[last_name]'] = last
-class Account():
- """This object is used to get access to user's settings on
- Diaspora* and provides interface for downloading user's stuff.
- """
- def __init__(self, connection):
- self._connection = connection
-
- def downloadxml(self):
- """Returns downloaded XML.
+ def setTags(self, tags):
+ """Sets tags that describe the user.
"""
- request = self._connection.get('user/export')
- return request.text
+ self.data['tags'] = ', '.join(['#{}'.format(tag) for tag in tags])
- def downloadPhotos(self, size='large', path='.', mark_nsfw=True, _critical=False, _stream=None):
- """Downloads photos into the current working directory.
- Sizes are: large, medium, small.
- Filename is: {post_guid}_{photo_guid}.{extension}
+ def setBio(self, bio):
+ """Set bio of a user.
+ """
+ self.data['profile[bio]'] = bio
- Normally, this method will catch urllib-generated errors and
- just issue warnings about photos that couldn't be downloaded.
- However, with _critical param set to True errors will become
- critical - the will be reraised in finally block.
+ def setLocation(self, location):
+ """Set location of a user.
+ """
+ self.data['profile[location]'] = location
- :param size: size of the photos to download - large, medium or small
- :type size: str
- :param path: path to download (defaults to current working directory
- :type path: str
- :param mark_nsfw: will append '-nsfw' to images from posts marked as nsfw,
- :type mark_nsfw: bool
- :param _stream: diaspy.streams.Generic-like object (only for testing)
- :param _critical: if True urllib errors will be reraised after generating a warning (may be removed)
+ def setGender(self, gender):
+ """Set gender of a user.
+ """
+ self.data['profile[gender]'] = gender
- :returns: integer, number of photos downloaded
+ def setBirthDate(self, year, month, day):
+ """Set birth date of a user.
"""
- photos = 0
- if _stream is None:
- stream = streams.Activity(self._connection)
- stream.full()
- else:
- stream = _stream
- for i, post in enumerate(stream):
- if post['nsfw'] is not False: nsfw = '-nsfw'
- else: nsfw = ''
- if post['photos']:
- for n, photo in enumerate(post['photos']):
- name = '{0}_{1}{2}.{3}'.format(post['guid'], photo['guid'], nsfw, photo['sizes'][size].split('.')[-1])
- filename = os.path.join(path, name)
- try:
- urllib.request.urlretrieve(url=photo['sizes'][size], filename=filename)
- except (urllib.error.HTTPError, urllib.error.URLError) as e:
- warnings.warn('downloading image {0} from post {1}: {2}'.format(photo['guid'], post['guid'], e))
- finally:
- if _critical: raise
- photos += 1
- return photos
+ self.data['profile[date][year]'] = year
+ self.data['profile[date][month]'] = month
+ self.data['profile[date][day]'] = day
- def setEmail(self, email):
- """Changes user's email.
+ def setSearchable(self, searchable):
+ """Set user's searchable status.
"""
- data = {'_method': 'put', 'utf8': '✓', 'user[email]': email, 'authenticity_token': repr(self._connection)}
- request = self._connection.post('user', data=data, allow_redirects=False)
+ self.data['profile[searchable]'] = json.dumps(searchable)
- def getEmail(self):
- """Returns currently used email.
+ def setNSFW(self, nsfw):
+ """Set user NSFW status.
"""
- data = self._connection.get('user/edit')
- email = re.compile('<input id="user_email" name="user\[email\]" size="30" type="text" value=".+?"').search(data.text)
- if email is None: raise errors.DiaspyError('cannot fetch email')
- email = email.group(0)[:-1]
- email = email[email.rfind('"')+1:]
- return email
+ self.data['profile[nsfw]'] = json.dumps(nsfw)
- def setLanguage(self, lang):
- """Changes user's email.
+ def load(self):
+ """Loads profile data into self.data dictionary.
+ **Notice:** Not all keys are loaded yet.
+ """
+ self.setName(*self.getName())
+ self.setBio(self.getBio())
+ self.setLocation(self.getLocation())
+ self.setGender(self.getGender())
+ self.setBirthDate(*self.getBirthDate(named_month=False))
+ self.setSearchable(self.isSearchable())
+ self.setNSFW(self.isNSFW())
+ self.setTags(self.getTags())
+ self._loaded = True
- :param lang: language identifier from getLanguages()
+ def update(self):
+ """Updates profile information.
"""
- data = {'_method': 'put', 'utf8': '✓', 'user[language]': lang, 'authenticity_token': repr(self._connection)}
- request = self._connection.post('user', data=data, allow_redirects=False)
+ if not self._loaded: raise errors.DiaspyError('profile was not loaded')
+ self.data['authenticity_token'] = repr(self._connection)
+ print(self.data)
+ request = self._connection.post('profile', data=self.data, allow_redirects=False)
return request.status_code
- def getLanguages(self):
- """Returns a list of tuples containing ('Language name', 'identifier').
- One of the Black Magic(tm) methods.
- """
- selection_start = '<select id="user_language" name="user[language]">'
- selection_end = '</select>'
- languages = []
- request = self._connection.get('user/edit')
- data = request.text[request.text.find(selection_start)+len(selection_start):]
- data = data[:data.find(selection_end)].split('\n')
- for item in data:
- name = item[item.find('>')+1:item.rfind('<')]
- identifier = item[item.find('"')+1:]
- identifier = identifier[:identifier.find('"')]
- languages.append((name, identifier))
- return languages
+
+class Services():
+ """Provides interface to services settings.
+ """
+ def __init__(self, connection):
+ self._connection = connection