| # -*-*- encoding: utf-8 -*-*- |
| # |
| # This is the base file for the PicasaWeb python client. |
| # It is used for lower level operations. |
| # |
| # $Id: __init__.py 148 2007-10-28 15:09:19Z havard.gulldahl $ |
| # |
| # Copyright 2007 Håvard Gulldahl |
| # Portions (C) 2006 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """This module provides a pythonic, gdata-centric interface to Google Photos |
| (a.k.a. Picasa Web Services. |
| |
| It is modelled after the gdata/* interfaces from the gdata-python-client |
| project[1] by Google. |
| |
| You'll find the user-friendly api in photos.service. Please see the |
| documentation or live help() system for available methods. |
| |
| [1]: http://gdata-python-client.googlecode.com/ |
| |
| """ |
| |
| __author__ = u'havard@gulldahl.no'# (Håvard Gulldahl)' #BUG: pydoc chokes on non-ascii chars in __author__ |
| __license__ = 'Apache License v2' |
| __version__ = '$Revision: 164 $'[11:-2] |
| |
| import re |
| try: |
| from xml.etree import cElementTree as ElementTree |
| except ImportError: |
| try: |
| import cElementTree as ElementTree |
| except ImportError: |
| try: |
| from xml.etree import ElementTree |
| except ImportError: |
| from elementtree import ElementTree |
| import atom |
| import gdata |
| |
| # importing google photo submodules |
| import gdata.media as Media, gdata.exif as Exif, gdata.geo as Geo |
| |
| # XML namespaces which are often used in Google Photo elements |
| PHOTOS_NAMESPACE = 'http://schemas.google.com/photos/2007' |
| MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/' |
| EXIF_NAMESPACE = 'http://schemas.google.com/photos/exif/2007' |
| OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/' |
| GEO_NAMESPACE = 'http://www.w3.org/2003/01/geo/wgs84_pos#' |
| GML_NAMESPACE = 'http://www.opengis.net/gml' |
| GEORSS_NAMESPACE = 'http://www.georss.org/georss' |
| PHEED_NAMESPACE = 'http://www.pheed.com/pheed/' |
| BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch' |
| |
| |
| class PhotosBaseElement(atom.AtomBase): |
| """Base class for elements in the PHOTO_NAMESPACE. To add new elements, |
| you only need to add the element tag name to self._tag |
| """ |
| |
| _tag = '' |
| _namespace = PHOTOS_NAMESPACE |
| _children = atom.AtomBase._children.copy() |
| _attributes = atom.AtomBase._attributes.copy() |
| |
| def __init__(self, name=None, extension_elements=None, |
| extension_attributes=None, text=None): |
| self.name = name |
| self.text = text |
| self.extension_elements = extension_elements or [] |
| self.extension_attributes = extension_attributes or {} |
| #def __str__(self): |
| #return str(self.text) |
| #def __unicode__(self): |
| #return unicode(self.text) |
| def __int__(self): |
| return int(self.text) |
| def bool(self): |
| return self.text == 'true' |
| |
| class GPhotosBaseFeed(gdata.GDataFeed, gdata.LinkFinder): |
| "Base class for all Feeds in gdata.photos" |
| _tag = 'feed' |
| _namespace = atom.ATOM_NAMESPACE |
| _attributes = gdata.GDataFeed._attributes.copy() |
| _children = gdata.GDataFeed._children.copy() |
| # We deal with Entry elements ourselves |
| del _children['{%s}entry' % atom.ATOM_NAMESPACE] |
| |
| def __init__(self, author=None, category=None, contributor=None, |
| generator=None, icon=None, atom_id=None, link=None, logo=None, |
| rights=None, subtitle=None, title=None, updated=None, |
| entry=None, total_results=None, start_index=None, |
| items_per_page=None, extension_elements=None, |
| extension_attributes=None, text=None): |
| gdata.GDataFeed.__init__(self, author=author, category=category, |
| contributor=contributor, generator=generator, |
| icon=icon, atom_id=atom_id, link=link, |
| logo=logo, rights=rights, subtitle=subtitle, |
| title=title, updated=updated, entry=entry, |
| total_results=total_results, |
| start_index=start_index, |
| items_per_page=items_per_page, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes, |
| text=text) |
| |
| def kind(self): |
| "(string) Returns the kind" |
| try: |
| return self.category[0].term.split('#')[1] |
| except IndexError: |
| return None |
| |
| def _feedUri(self, kind): |
| "Convenience method to return a uri to a feed of a special kind" |
| assert(kind in ('album', 'tag', 'photo', 'comment', 'user')) |
| here_href = self.GetSelfLink().href |
| if 'kind=%s' % kind in here_href: |
| return here_href |
| if not 'kind=' in here_href: |
| sep = '?' |
| if '?' in here_href: sep = '&' |
| return here_href + "%skind=%s" % (sep, kind) |
| rx = re.match('.*(kind=)(album|tag|photo|comment)', here_href) |
| return here_href[:rx.end(1)] + kind + here_href[rx.end(2):] |
| |
| def _ConvertElementTreeToMember(self, child_tree): |
| """Re-implementing the method from AtomBase, since we deal with |
| Entry elements specially""" |
| category = child_tree.find('{%s}category' % atom.ATOM_NAMESPACE) |
| if category is None: |
| return atom.AtomBase._ConvertElementTreeToMember(self, child_tree) |
| namespace, kind = category.get('term').split('#') |
| if namespace != PHOTOS_NAMESPACE: |
| return atom.AtomBase._ConvertElementTreeToMember(self, child_tree) |
| ## TODO: is it safe to use getattr on gdata.photos? |
| entry_class = getattr(gdata.photos, '%sEntry' % kind.title()) |
| if not hasattr(self, 'entry') or self.entry is None: |
| self.entry = [] |
| self.entry.append(atom._CreateClassFromElementTree( |
| entry_class, child_tree)) |
| |
| class GPhotosBaseEntry(gdata.GDataEntry, gdata.LinkFinder): |
| "Base class for all Entry elements in gdata.photos" |
| _tag = 'entry' |
| _kind = '' |
| _namespace = atom.ATOM_NAMESPACE |
| _children = gdata.GDataEntry._children.copy() |
| _attributes = gdata.GDataEntry._attributes.copy() |
| |
| def __init__(self, author=None, category=None, content=None, |
| atom_id=None, link=None, published=None, |
| title=None, updated=None, |
| extended_property=None, |
| extension_elements=None, extension_attributes=None, text=None): |
| gdata.GDataEntry.__init__(self, author=author, category=category, |
| content=content, atom_id=atom_id, link=link, |
| published=published, title=title, |
| updated=updated, text=text, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes) |
| self.category.append( |
| atom.Category(scheme='http://schemas.google.com/g/2005#kind', |
| term = 'http://schemas.google.com/photos/2007#%s' % self._kind)) |
| |
| def kind(self): |
| "(string) Returns the kind" |
| try: |
| return self.category[0].term.split('#')[1] |
| except IndexError: |
| return None |
| |
| def _feedUri(self, kind): |
| "Convenience method to get the uri to this entry's feed of the some kind" |
| try: |
| href = self.GetFeedLink().href |
| except AttributeError: |
| return None |
| sep = '?' |
| if '?' in href: sep = '&' |
| return '%s%skind=%s' % (href, sep, kind) |
| |
| |
| class PhotosBaseEntry(GPhotosBaseEntry): |
| pass |
| |
| class PhotosBaseFeed(GPhotosBaseFeed): |
| pass |
| |
| class GPhotosBaseData(object): |
| pass |
| |
| class Access(PhotosBaseElement): |
| """The Google Photo `Access' element. |
| |
| The album's access level. Valid values are `public' or `private'. |
| In documentation, access level is also referred to as `visibility.'""" |
| |
| _tag = 'access' |
| def AccessFromString(xml_string): |
| return atom.CreateClassFromXMLString(Access, xml_string) |
| |
| class Albumid(PhotosBaseElement): |
| "The Google Photo `Albumid' element" |
| |
| _tag = 'albumid' |
| def AlbumidFromString(xml_string): |
| return atom.CreateClassFromXMLString(Albumid, xml_string) |
| |
| class BytesUsed(PhotosBaseElement): |
| "The Google Photo `BytesUsed' element" |
| |
| _tag = 'bytesUsed' |
| def BytesUsedFromString(xml_string): |
| return atom.CreateClassFromXMLString(BytesUsed, xml_string) |
| |
| class Client(PhotosBaseElement): |
| "The Google Photo `Client' element" |
| |
| _tag = 'client' |
| def ClientFromString(xml_string): |
| return atom.CreateClassFromXMLString(Client, xml_string) |
| |
| class Checksum(PhotosBaseElement): |
| "The Google Photo `Checksum' element" |
| |
| _tag = 'checksum' |
| def ChecksumFromString(xml_string): |
| return atom.CreateClassFromXMLString(Checksum, xml_string) |
| |
| class CommentCount(PhotosBaseElement): |
| "The Google Photo `CommentCount' element" |
| |
| _tag = 'commentCount' |
| def CommentCountFromString(xml_string): |
| return atom.CreateClassFromXMLString(CommentCount, xml_string) |
| |
| class CommentingEnabled(PhotosBaseElement): |
| "The Google Photo `CommentingEnabled' element" |
| |
| _tag = 'commentingEnabled' |
| def CommentingEnabledFromString(xml_string): |
| return atom.CreateClassFromXMLString(CommentingEnabled, xml_string) |
| |
| class Height(PhotosBaseElement): |
| "The Google Photo `Height' element" |
| |
| _tag = 'height' |
| def HeightFromString(xml_string): |
| return atom.CreateClassFromXMLString(Height, xml_string) |
| |
| class Id(PhotosBaseElement): |
| "The Google Photo `Id' element" |
| |
| _tag = 'id' |
| def IdFromString(xml_string): |
| return atom.CreateClassFromXMLString(Id, xml_string) |
| |
| class Location(PhotosBaseElement): |
| "The Google Photo `Location' element" |
| |
| _tag = 'location' |
| def LocationFromString(xml_string): |
| return atom.CreateClassFromXMLString(Location, xml_string) |
| |
| class MaxPhotosPerAlbum(PhotosBaseElement): |
| "The Google Photo `MaxPhotosPerAlbum' element" |
| |
| _tag = 'maxPhotosPerAlbum' |
| def MaxPhotosPerAlbumFromString(xml_string): |
| return atom.CreateClassFromXMLString(MaxPhotosPerAlbum, xml_string) |
| |
| class Name(PhotosBaseElement): |
| "The Google Photo `Name' element" |
| |
| _tag = 'name' |
| def NameFromString(xml_string): |
| return atom.CreateClassFromXMLString(Name, xml_string) |
| |
| class Nickname(PhotosBaseElement): |
| "The Google Photo `Nickname' element" |
| |
| _tag = 'nickname' |
| def NicknameFromString(xml_string): |
| return atom.CreateClassFromXMLString(Nickname, xml_string) |
| |
| class Numphotos(PhotosBaseElement): |
| "The Google Photo `Numphotos' element" |
| |
| _tag = 'numphotos' |
| def NumphotosFromString(xml_string): |
| return atom.CreateClassFromXMLString(Numphotos, xml_string) |
| |
| class Numphotosremaining(PhotosBaseElement): |
| "The Google Photo `Numphotosremaining' element" |
| |
| _tag = 'numphotosremaining' |
| def NumphotosremainingFromString(xml_string): |
| return atom.CreateClassFromXMLString(Numphotosremaining, xml_string) |
| |
| class Position(PhotosBaseElement): |
| "The Google Photo `Position' element" |
| |
| _tag = 'position' |
| def PositionFromString(xml_string): |
| return atom.CreateClassFromXMLString(Position, xml_string) |
| |
| class Photoid(PhotosBaseElement): |
| "The Google Photo `Photoid' element" |
| |
| _tag = 'photoid' |
| def PhotoidFromString(xml_string): |
| return atom.CreateClassFromXMLString(Photoid, xml_string) |
| |
| class Quotacurrent(PhotosBaseElement): |
| "The Google Photo `Quotacurrent' element" |
| |
| _tag = 'quotacurrent' |
| def QuotacurrentFromString(xml_string): |
| return atom.CreateClassFromXMLString(Quotacurrent, xml_string) |
| |
| class Quotalimit(PhotosBaseElement): |
| "The Google Photo `Quotalimit' element" |
| |
| _tag = 'quotalimit' |
| def QuotalimitFromString(xml_string): |
| return atom.CreateClassFromXMLString(Quotalimit, xml_string) |
| |
| class Rotation(PhotosBaseElement): |
| "The Google Photo `Rotation' element" |
| |
| _tag = 'rotation' |
| def RotationFromString(xml_string): |
| return atom.CreateClassFromXMLString(Rotation, xml_string) |
| |
| class Size(PhotosBaseElement): |
| "The Google Photo `Size' element" |
| |
| _tag = 'size' |
| def SizeFromString(xml_string): |
| return atom.CreateClassFromXMLString(Size, xml_string) |
| |
| class Snippet(PhotosBaseElement): |
| """The Google Photo `snippet' element. |
| |
| When searching, the snippet element will contain a |
| string with the word you're looking for, highlighted in html markup |
| E.g. when your query is `hafjell', this element may contain: |
| `... here at <b>Hafjell</b>.' |
| |
| You'll find this element in searches -- that is, feeds that combine the |
| `kind=photo' and `q=yoursearch' parameters in the request. |
| |
| See also gphoto:truncated and gphoto:snippettype. |
| |
| """ |
| |
| _tag = 'snippet' |
| def SnippetFromString(xml_string): |
| return atom.CreateClassFromXMLString(Snippet, xml_string) |
| |
| class Snippettype(PhotosBaseElement): |
| """The Google Photo `Snippettype' element |
| |
| When searching, this element will tell you the type of element that matches. |
| |
| You'll find this element in searches -- that is, feeds that combine the |
| `kind=photo' and `q=yoursearch' parameters in the request. |
| |
| See also gphoto:snippet and gphoto:truncated. |
| |
| Possible values and their interpretation: |
| o ALBUM_TITLE - The album title matches |
| o PHOTO_TAGS - The match is a tag/keyword |
| o PHOTO_DESCRIPTION - The match is in the photo's description |
| |
| If you discover a value not listed here, please submit a patch to update this docstring. |
| |
| """ |
| |
| _tag = 'snippettype' |
| def SnippettypeFromString(xml_string): |
| return atom.CreateClassFromXMLString(Snippettype, xml_string) |
| |
| class Thumbnail(PhotosBaseElement): |
| """The Google Photo `Thumbnail' element |
| |
| Used to display user's photo thumbnail (hackergotchi). |
| |
| (Not to be confused with the <media:thumbnail> element, which gives you |
| small versions of the photo object.)""" |
| |
| _tag = 'thumbnail' |
| def ThumbnailFromString(xml_string): |
| return atom.CreateClassFromXMLString(Thumbnail, xml_string) |
| |
| class Timestamp(PhotosBaseElement): |
| """The Google Photo `Timestamp' element |
| Represented as the number of milliseconds since January 1st, 1970. |
| |
| |
| Take a look at the convenience methods .isoformat() and .datetime(): |
| |
| photo_epoch = Time.text # 1180294337000 |
| photo_isostring = Time.isoformat() # '2007-05-27T19:32:17.000Z' |
| |
| Alternatively: |
| photo_datetime = Time.datetime() # (requires python >= 2.3) |
| """ |
| |
| _tag = 'timestamp' |
| def isoformat(self): |
| """(string) Return the timestamp as a ISO 8601 formatted string, |
| e.g. '2007-05-27T19:32:17.000Z' |
| """ |
| import time |
| epoch = float(self.text)/1000 |
| return time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(epoch)) |
| |
| def datetime(self): |
| """(datetime.datetime) Return the timestamp as a datetime.datetime object |
| |
| Requires python 2.3 |
| """ |
| import datetime |
| epoch = float(self.text)/1000 |
| return datetime.datetime.fromtimestamp(epoch) |
| def TimestampFromString(xml_string): |
| return atom.CreateClassFromXMLString(Timestamp, xml_string) |
| |
| class Truncated(PhotosBaseElement): |
| """The Google Photo `Truncated' element |
| |
| You'll find this element in searches -- that is, feeds that combine the |
| `kind=photo' and `q=yoursearch' parameters in the request. |
| |
| See also gphoto:snippet and gphoto:snippettype. |
| |
| Possible values and their interpretation: |
| 0 -- unknown |
| """ |
| |
| _tag = 'Truncated' |
| def TruncatedFromString(xml_string): |
| return atom.CreateClassFromXMLString(Truncated, xml_string) |
| |
| class User(PhotosBaseElement): |
| "The Google Photo `User' element" |
| |
| _tag = 'user' |
| def UserFromString(xml_string): |
| return atom.CreateClassFromXMLString(User, xml_string) |
| |
| class Version(PhotosBaseElement): |
| "The Google Photo `Version' element" |
| |
| _tag = 'version' |
| def VersionFromString(xml_string): |
| return atom.CreateClassFromXMLString(Version, xml_string) |
| |
| class Width(PhotosBaseElement): |
| "The Google Photo `Width' element" |
| |
| _tag = 'width' |
| def WidthFromString(xml_string): |
| return atom.CreateClassFromXMLString(Width, xml_string) |
| |
| class Weight(PhotosBaseElement): |
| """The Google Photo `Weight' element. |
| |
| The weight of the tag is the number of times the tag |
| appears in the collection of tags currently being viewed. |
| The default weight is 1, in which case this tags is omitted.""" |
| _tag = 'weight' |
| def WeightFromString(xml_string): |
| return atom.CreateClassFromXMLString(Weight, xml_string) |
| |
| class CommentAuthor(atom.Author): |
| """The Atom `Author' element in CommentEntry entries is augmented to |
| contain elements from the PHOTOS_NAMESPACE |
| |
| http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38 |
| """ |
| _children = atom.Author._children.copy() |
| _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User) |
| _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname) |
| _children['{%s}thumbnail' % PHOTOS_NAMESPACE] = ('thumbnail', Thumbnail) |
| def CommentAuthorFromString(xml_string): |
| return atom.CreateClassFromXMLString(CommentAuthor, xml_string) |
| |
| ########################## ################################ |
| |
| class AlbumData(object): |
| _children = {} |
| _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) |
| _children['{%s}name' % PHOTOS_NAMESPACE] = ('name', Name) |
| _children['{%s}location' % PHOTOS_NAMESPACE] = ('location', Location) |
| _children['{%s}access' % PHOTOS_NAMESPACE] = ('access', Access) |
| _children['{%s}bytesUsed' % PHOTOS_NAMESPACE] = ('bytesUsed', BytesUsed) |
| _children['{%s}timestamp' % PHOTOS_NAMESPACE] = ('timestamp', Timestamp) |
| _children['{%s}numphotos' % PHOTOS_NAMESPACE] = ('numphotos', Numphotos) |
| _children['{%s}numphotosremaining' % PHOTOS_NAMESPACE] = \ |
| ('numphotosremaining', Numphotosremaining) |
| _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User) |
| _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname) |
| _children['{%s}commentingEnabled' % PHOTOS_NAMESPACE] = \ |
| ('commentingEnabled', CommentingEnabled) |
| _children['{%s}commentCount' % PHOTOS_NAMESPACE] = \ |
| ('commentCount', CommentCount) |
| ## NOTE: storing media:group as self.media, to create a self-explaining api |
| gphoto_id = None |
| name = None |
| location = None |
| access = None |
| bytesUsed = None |
| timestamp = None |
| numphotos = None |
| numphotosremaining = None |
| user = None |
| nickname = None |
| commentingEnabled = None |
| commentCount = None |
| |
| class AlbumEntry(GPhotosBaseEntry, AlbumData): |
| """All metadata for a Google Photos Album |
| |
| Take a look at AlbumData for metadata accessible as attributes to this object. |
| |
| Notes: |
| To avoid name clashes, and to create a more sensible api, some |
| objects have names that differ from the original elements: |
| |
| o media:group -> self.media, |
| o geo:where -> self.geo, |
| o photo:id -> self.gphoto_id |
| """ |
| |
| _kind = 'album' |
| _children = GPhotosBaseEntry._children.copy() |
| _children.update(AlbumData._children.copy()) |
| # child tags only for Album entries, not feeds |
| _children['{%s}where' % GEORSS_NAMESPACE] = ('geo', Geo.Where) |
| _children['{%s}group' % MEDIA_NAMESPACE] = ('media', Media.Group) |
| media = Media.Group() |
| geo = Geo.Where() |
| |
| def __init__(self, author=None, category=None, content=None, |
| atom_id=None, link=None, published=None, |
| title=None, updated=None, |
| #GPHOTO NAMESPACE: |
| gphoto_id=None, name=None, location=None, access=None, |
| timestamp=None, numphotos=None, user=None, nickname=None, |
| commentingEnabled=None, commentCount=None, thumbnail=None, |
| # MEDIA NAMESPACE: |
| media=None, |
| # GEORSS NAMESPACE: |
| geo=None, |
| extended_property=None, |
| extension_elements=None, extension_attributes=None, text=None): |
| GPhotosBaseEntry.__init__(self, author=author, category=category, |
| content=content, atom_id=atom_id, link=link, |
| published=published, title=title, |
| updated=updated, text=text, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes) |
| |
| ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id |
| self.gphoto_id = gphoto_id |
| self.name = name |
| self.location = location |
| self.access = access |
| self.timestamp = timestamp |
| self.numphotos = numphotos |
| self.user = user |
| self.nickname = nickname |
| self.commentingEnabled = commentingEnabled |
| self.commentCount = commentCount |
| self.thumbnail = thumbnail |
| self.extended_property = extended_property or [] |
| self.text = text |
| ## NOTE: storing media:group as self.media, and geo:where as geo, |
| ## to create a self-explaining api |
| self.media = media or Media.Group() |
| self.geo = geo or Geo.Where() |
| |
| def GetAlbumId(self): |
| "Return the id of this album" |
| |
| return self.GetFeedLink().href.split('/')[-1] |
| |
| def GetPhotosUri(self): |
| "(string) Return the uri to this albums feed of the PhotoEntry kind" |
| return self._feedUri('photo') |
| |
| def GetCommentsUri(self): |
| "(string) Return the uri to this albums feed of the CommentEntry kind" |
| return self._feedUri('comment') |
| |
| def GetTagsUri(self): |
| "(string) Return the uri to this albums feed of the TagEntry kind" |
| return self._feedUri('tag') |
| |
| def AlbumEntryFromString(xml_string): |
| return atom.CreateClassFromXMLString(AlbumEntry, xml_string) |
| |
| class AlbumFeed(GPhotosBaseFeed, AlbumData): |
| """All metadata for a Google Photos Album, including its sub-elements |
| |
| This feed represents an album as the container for other objects. |
| |
| A Album feed contains entries of |
| PhotoEntry, CommentEntry or TagEntry, |
| depending on the `kind' parameter in the original query. |
| |
| Take a look at AlbumData for accessible attributes. |
| |
| """ |
| |
| _children = GPhotosBaseFeed._children.copy() |
| _children.update(AlbumData._children.copy()) |
| |
| def GetPhotosUri(self): |
| "(string) Return the uri to the same feed, but of the PhotoEntry kind" |
| |
| return self._feedUri('photo') |
| |
| def GetTagsUri(self): |
| "(string) Return the uri to the same feed, but of the TagEntry kind" |
| |
| return self._feedUri('tag') |
| |
| def GetCommentsUri(self): |
| "(string) Return the uri to the same feed, but of the CommentEntry kind" |
| |
| return self._feedUri('comment') |
| |
| def AlbumFeedFromString(xml_string): |
| return atom.CreateClassFromXMLString(AlbumFeed, xml_string) |
| |
| |
| class PhotoData(object): |
| _children = {} |
| ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id |
| _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) |
| _children['{%s}albumid' % PHOTOS_NAMESPACE] = ('albumid', Albumid) |
| _children['{%s}checksum' % PHOTOS_NAMESPACE] = ('checksum', Checksum) |
| _children['{%s}client' % PHOTOS_NAMESPACE] = ('client', Client) |
| _children['{%s}height' % PHOTOS_NAMESPACE] = ('height', Height) |
| _children['{%s}position' % PHOTOS_NAMESPACE] = ('position', Position) |
| _children['{%s}rotation' % PHOTOS_NAMESPACE] = ('rotation', Rotation) |
| _children['{%s}size' % PHOTOS_NAMESPACE] = ('size', Size) |
| _children['{%s}timestamp' % PHOTOS_NAMESPACE] = ('timestamp', Timestamp) |
| _children['{%s}version' % PHOTOS_NAMESPACE] = ('version', Version) |
| _children['{%s}width' % PHOTOS_NAMESPACE] = ('width', Width) |
| _children['{%s}commentingEnabled' % PHOTOS_NAMESPACE] = \ |
| ('commentingEnabled', CommentingEnabled) |
| _children['{%s}commentCount' % PHOTOS_NAMESPACE] = \ |
| ('commentCount', CommentCount) |
| ## NOTE: storing media:group as self.media, exif:tags as self.exif, and |
| ## geo:where as self.geo, to create a self-explaining api |
| _children['{%s}tags' % EXIF_NAMESPACE] = ('exif', Exif.Tags) |
| _children['{%s}where' % GEORSS_NAMESPACE] = ('geo', Geo.Where) |
| _children['{%s}group' % MEDIA_NAMESPACE] = ('media', Media.Group) |
| # These elements show up in search feeds |
| _children['{%s}snippet' % PHOTOS_NAMESPACE] = ('snippet', Snippet) |
| _children['{%s}snippettype' % PHOTOS_NAMESPACE] = ('snippettype', Snippettype) |
| _children['{%s}truncated' % PHOTOS_NAMESPACE] = ('truncated', Truncated) |
| gphoto_id = None |
| albumid = None |
| checksum = None |
| client = None |
| height = None |
| position = None |
| rotation = None |
| size = None |
| timestamp = None |
| version = None |
| width = None |
| commentingEnabled = None |
| commentCount = None |
| snippet=None |
| snippettype=None |
| truncated=None |
| media = Media.Group() |
| geo = Geo.Where() |
| tags = Exif.Tags() |
| |
| class PhotoEntry(GPhotosBaseEntry, PhotoData): |
| """All metadata for a Google Photos Photo |
| |
| Take a look at PhotoData for metadata accessible as attributes to this object. |
| |
| Notes: |
| To avoid name clashes, and to create a more sensible api, some |
| objects have names that differ from the original elements: |
| |
| o media:group -> self.media, |
| o exif:tags -> self.exif, |
| o geo:where -> self.geo, |
| o photo:id -> self.gphoto_id |
| """ |
| |
| _kind = 'photo' |
| _children = GPhotosBaseEntry._children.copy() |
| _children.update(PhotoData._children.copy()) |
| |
| def __init__(self, author=None, category=None, content=None, |
| atom_id=None, link=None, published=None, |
| title=None, updated=None, text=None, |
| # GPHOTO NAMESPACE: |
| gphoto_id=None, albumid=None, checksum=None, client=None, height=None, |
| position=None, rotation=None, size=None, timestamp=None, version=None, |
| width=None, commentCount=None, commentingEnabled=None, |
| # MEDIARSS NAMESPACE: |
| media=None, |
| # EXIF_NAMESPACE: |
| exif=None, |
| # GEORSS NAMESPACE: |
| geo=None, |
| extension_elements=None, extension_attributes=None): |
| GPhotosBaseEntry.__init__(self, author=author, category=category, |
| content=content, |
| atom_id=atom_id, link=link, published=published, |
| title=title, updated=updated, text=text, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes) |
| |
| |
| ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id |
| self.gphoto_id = gphoto_id |
| self.albumid = albumid |
| self.checksum = checksum |
| self.client = client |
| self.height = height |
| self.position = position |
| self.rotation = rotation |
| self.size = size |
| self.timestamp = timestamp |
| self.version = version |
| self.width = width |
| self.commentingEnabled = commentingEnabled |
| self.commentCount = commentCount |
| ## NOTE: storing media:group as self.media, to create a self-explaining api |
| self.media = media or Media.Group() |
| self.exif = exif or Exif.Tags() |
| self.geo = geo or Geo.Where() |
| |
| def GetPostLink(self): |
| "Return the uri to this photo's `POST' link (use it for updates of the object)" |
| |
| return self.GetFeedLink() |
| |
| def GetCommentsUri(self): |
| "Return the uri to this photo's feed of CommentEntry comments" |
| return self._feedUri('comment') |
| |
| def GetTagsUri(self): |
| "Return the uri to this photo's feed of TagEntry tags" |
| return self._feedUri('tag') |
| |
| def GetAlbumUri(self): |
| """Return the uri to the AlbumEntry containing this photo""" |
| |
| href = self.GetSelfLink().href |
| return href[:href.find('/photoid')] |
| |
| def PhotoEntryFromString(xml_string): |
| return atom.CreateClassFromXMLString(PhotoEntry, xml_string) |
| |
| class PhotoFeed(GPhotosBaseFeed, PhotoData): |
| """All metadata for a Google Photos Photo, including its sub-elements |
| |
| This feed represents a photo as the container for other objects. |
| |
| A Photo feed contains entries of |
| CommentEntry or TagEntry, |
| depending on the `kind' parameter in the original query. |
| |
| Take a look at PhotoData for metadata accessible as attributes to this object. |
| |
| """ |
| _children = GPhotosBaseFeed._children.copy() |
| _children.update(PhotoData._children.copy()) |
| |
| def GetTagsUri(self): |
| "(string) Return the uri to the same feed, but of the TagEntry kind" |
| |
| return self._feedUri('tag') |
| |
| def GetCommentsUri(self): |
| "(string) Return the uri to the same feed, but of the CommentEntry kind" |
| |
| return self._feedUri('comment') |
| |
| def PhotoFeedFromString(xml_string): |
| return atom.CreateClassFromXMLString(PhotoFeed, xml_string) |
| |
| class TagData(GPhotosBaseData): |
| _children = {} |
| _children['{%s}weight' % PHOTOS_NAMESPACE] = ('weight', Weight) |
| weight=None |
| |
| class TagEntry(GPhotosBaseEntry, TagData): |
| """All metadata for a Google Photos Tag |
| |
| The actual tag is stored in the .title.text attribute |
| |
| """ |
| |
| _kind = 'tag' |
| _children = GPhotosBaseEntry._children.copy() |
| _children.update(TagData._children.copy()) |
| |
| def __init__(self, author=None, category=None, content=None, |
| atom_id=None, link=None, published=None, |
| title=None, updated=None, |
| # GPHOTO NAMESPACE: |
| weight=None, |
| extended_property=None, |
| extension_elements=None, extension_attributes=None, text=None): |
| GPhotosBaseEntry.__init__(self, author=author, category=category, |
| content=content, |
| atom_id=atom_id, link=link, published=published, |
| title=title, updated=updated, text=text, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes) |
| |
| self.weight = weight |
| |
| def GetAlbumUri(self): |
| """Return the uri to the AlbumEntry containing this tag""" |
| |
| href = self.GetSelfLink().href |
| pos = href.find('/photoid') |
| if pos == -1: |
| return None |
| return href[:pos] |
| |
| def GetPhotoUri(self): |
| """Return the uri to the PhotoEntry containing this tag""" |
| |
| href = self.GetSelfLink().href |
| pos = href.find('/tag') |
| if pos == -1: |
| return None |
| return href[:pos] |
| |
| def TagEntryFromString(xml_string): |
| return atom.CreateClassFromXMLString(TagEntry, xml_string) |
| |
| |
| class TagFeed(GPhotosBaseFeed, TagData): |
| """All metadata for a Google Photos Tag, including its sub-elements""" |
| |
| _children = GPhotosBaseFeed._children.copy() |
| _children.update(TagData._children.copy()) |
| |
| def TagFeedFromString(xml_string): |
| return atom.CreateClassFromXMLString(TagFeed, xml_string) |
| |
| class CommentData(GPhotosBaseData): |
| _children = {} |
| ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id |
| _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) |
| _children['{%s}albumid' % PHOTOS_NAMESPACE] = ('albumid', Albumid) |
| _children['{%s}photoid' % PHOTOS_NAMESPACE] = ('photoid', Photoid) |
| _children['{%s}author' % atom.ATOM_NAMESPACE] = ('author', [CommentAuthor,]) |
| gphoto_id=None |
| albumid=None |
| photoid=None |
| author=None |
| |
| class CommentEntry(GPhotosBaseEntry, CommentData): |
| """All metadata for a Google Photos Comment |
| |
| The comment is stored in the .content.text attribute, |
| with a content type in .content.type. |
| |
| |
| """ |
| |
| _kind = 'comment' |
| _children = GPhotosBaseEntry._children.copy() |
| _children.update(CommentData._children.copy()) |
| def __init__(self, author=None, category=None, content=None, |
| atom_id=None, link=None, published=None, |
| title=None, updated=None, |
| # GPHOTO NAMESPACE: |
| gphoto_id=None, albumid=None, photoid=None, |
| extended_property=None, |
| extension_elements=None, extension_attributes=None, text=None): |
| |
| GPhotosBaseEntry.__init__(self, author=author, category=category, |
| content=content, |
| atom_id=atom_id, link=link, published=published, |
| title=title, updated=updated, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes, |
| text=text) |
| |
| self.gphoto_id = gphoto_id |
| self.albumid = albumid |
| self.photoid = photoid |
| |
| def GetCommentId(self): |
| """Return the globally unique id of this comment""" |
| return self.GetSelfLink().href.split('/')[-1] |
| |
| def GetAlbumUri(self): |
| """Return the uri to the AlbumEntry containing this comment""" |
| |
| href = self.GetSelfLink().href |
| return href[:href.find('/photoid')] |
| |
| def GetPhotoUri(self): |
| """Return the uri to the PhotoEntry containing this comment""" |
| |
| href = self.GetSelfLink().href |
| return href[:href.find('/commentid')] |
| |
| def CommentEntryFromString(xml_string): |
| return atom.CreateClassFromXMLString(CommentEntry, xml_string) |
| |
| class CommentFeed(GPhotosBaseFeed, CommentData): |
| """All metadata for a Google Photos Comment, including its sub-elements""" |
| |
| _children = GPhotosBaseFeed._children.copy() |
| _children.update(CommentData._children.copy()) |
| |
| def CommentFeedFromString(xml_string): |
| return atom.CreateClassFromXMLString(CommentFeed, xml_string) |
| |
| class UserData(GPhotosBaseData): |
| _children = {} |
| _children['{%s}maxPhotosPerAlbum' % PHOTOS_NAMESPACE] = ('maxPhotosPerAlbum', MaxPhotosPerAlbum) |
| _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname) |
| _children['{%s}quotalimit' % PHOTOS_NAMESPACE] = ('quotalimit', Quotalimit) |
| _children['{%s}quotacurrent' % PHOTOS_NAMESPACE] = ('quotacurrent', Quotacurrent) |
| _children['{%s}thumbnail' % PHOTOS_NAMESPACE] = ('thumbnail', Thumbnail) |
| _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User) |
| _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id) |
| |
| maxPhotosPerAlbum=None |
| nickname=None |
| quotalimit=None |
| quotacurrent=None |
| thumbnail=None |
| user=None |
| gphoto_id=None |
| |
| |
| class UserEntry(GPhotosBaseEntry, UserData): |
| """All metadata for a Google Photos User |
| |
| This entry represents an album owner and all appropriate metadata. |
| |
| Take a look at at the attributes of the UserData for metadata available. |
| """ |
| _children = GPhotosBaseEntry._children.copy() |
| _children.update(UserData._children.copy()) |
| _kind = 'user' |
| |
| def __init__(self, author=None, category=None, content=None, |
| atom_id=None, link=None, published=None, |
| title=None, updated=None, |
| # GPHOTO NAMESPACE: |
| gphoto_id=None, maxPhotosPerAlbum=None, nickname=None, quotalimit=None, |
| quotacurrent=None, thumbnail=None, user=None, |
| extended_property=None, |
| extension_elements=None, extension_attributes=None, text=None): |
| |
| GPhotosBaseEntry.__init__(self, author=author, category=category, |
| content=content, |
| atom_id=atom_id, link=link, published=published, |
| title=title, updated=updated, |
| extension_elements=extension_elements, |
| extension_attributes=extension_attributes, |
| text=text) |
| |
| |
| self.gphoto_id=gphoto_id |
| self.maxPhotosPerAlbum=maxPhotosPerAlbum |
| self.nickname=nickname |
| self.quotalimit=quotalimit |
| self.quotacurrent=quotacurrent |
| self.thumbnail=thumbnail |
| self.user=user |
| |
| def GetAlbumsUri(self): |
| "(string) Return the uri to this user's feed of the AlbumEntry kind" |
| return self._feedUri('album') |
| |
| def GetPhotosUri(self): |
| "(string) Return the uri to this user's feed of the PhotoEntry kind" |
| return self._feedUri('photo') |
| |
| def GetCommentsUri(self): |
| "(string) Return the uri to this user's feed of the CommentEntry kind" |
| return self._feedUri('comment') |
| |
| def GetTagsUri(self): |
| "(string) Return the uri to this user's feed of the TagEntry kind" |
| return self._feedUri('tag') |
| |
| def UserEntryFromString(xml_string): |
| return atom.CreateClassFromXMLString(UserEntry, xml_string) |
| |
| class UserFeed(GPhotosBaseFeed, UserData): |
| """Feed for a User in the google photos api. |
| |
| This feed represents a user as the container for other objects. |
| |
| A User feed contains entries of |
| AlbumEntry, PhotoEntry, CommentEntry, UserEntry or TagEntry, |
| depending on the `kind' parameter in the original query. |
| |
| The user feed itself also contains all of the metadata available |
| as part of a UserData object.""" |
| _children = GPhotosBaseFeed._children.copy() |
| _children.update(UserData._children.copy()) |
| |
| def GetAlbumsUri(self): |
| """Get the uri to this feed, but with entries of the AlbumEntry kind.""" |
| return self._feedUri('album') |
| |
| def GetTagsUri(self): |
| """Get the uri to this feed, but with entries of the TagEntry kind.""" |
| return self._feedUri('tag') |
| |
| def GetPhotosUri(self): |
| """Get the uri to this feed, but with entries of the PhotosEntry kind.""" |
| return self._feedUri('photo') |
| |
| def GetCommentsUri(self): |
| """Get the uri to this feed, but with entries of the CommentsEntry kind.""" |
| return self._feedUri('comment') |
| |
| def UserFeedFromString(xml_string): |
| return atom.CreateClassFromXMLString(UserFeed, xml_string) |
| |
| |
| |
| def AnyFeedFromString(xml_string): |
| """Creates an instance of the appropriate feed class from the |
| xml string contents. |
| |
| Args: |
| xml_string: str A string which contains valid XML. The root element |
| of the XML string should match the tag and namespace of the desired |
| class. |
| |
| Returns: |
| An instance of the target class with members assigned according to the |
| contents of the XML - or a basic gdata.GDataFeed instance if it is |
| impossible to determine the appropriate class (look for extra elements |
| in GDataFeed's .FindExtensions() and extension_elements[] ). |
| """ |
| tree = ElementTree.fromstring(xml_string) |
| category = tree.find('{%s}category' % atom.ATOM_NAMESPACE) |
| if category is None: |
| # TODO: is this the best way to handle this? |
| return atom._CreateClassFromElementTree(GPhotosBaseFeed, tree) |
| namespace, kind = category.get('term').split('#') |
| if namespace != PHOTOS_NAMESPACE: |
| # TODO: is this the best way to handle this? |
| return atom._CreateClassFromElementTree(GPhotosBaseFeed, tree) |
| ## TODO: is getattr safe this way? |
| feed_class = getattr(gdata.photos, '%sFeed' % kind.title()) |
| return atom._CreateClassFromElementTree(feed_class, tree) |
| |
| def AnyEntryFromString(xml_string): |
| """Creates an instance of the appropriate entry class from the |
| xml string contents. |
| |
| Args: |
| xml_string: str A string which contains valid XML. The root element |
| of the XML string should match the tag and namespace of the desired |
| class. |
| |
| Returns: |
| An instance of the target class with members assigned according to the |
| contents of the XML - or a basic gdata.GDataEndry instance if it is |
| impossible to determine the appropriate class (look for extra elements |
| in GDataEntry's .FindExtensions() and extension_elements[] ). |
| """ |
| tree = ElementTree.fromstring(xml_string) |
| category = tree.find('{%s}category' % atom.ATOM_NAMESPACE) |
| if category is None: |
| # TODO: is this the best way to handle this? |
| return atom._CreateClassFromElementTree(GPhotosBaseEntry, tree) |
| namespace, kind = category.get('term').split('#') |
| if namespace != PHOTOS_NAMESPACE: |
| # TODO: is this the best way to handle this? |
| return atom._CreateClassFromElementTree(GPhotosBaseEntry, tree) |
| ## TODO: is getattr safe this way? |
| feed_class = getattr(gdata.photos, '%sEntry' % kind.title()) |
| return atom._CreateClassFromElementTree(feed_class, tree) |
| |