/gdata/photos/__init__.py

http://radioappz.googlecode.com/ · Python · 1112 lines · 980 code · 51 blank · 81 comment · 48 complexity · 4c35c94c27b474ca0088f8b0c41d238f MD5 · raw file

  1. # -*-*- encoding: utf-8 -*-*-
  2. #
  3. # This is the base file for the PicasaWeb python client.
  4. # It is used for lower level operations.
  5. #
  6. # $Id: __init__.py 148 2007-10-28 15:09:19Z havard.gulldahl $
  7. #
  8. # Copyright 2007 H?vard Gulldahl
  9. # Portions (C) 2006 Google Inc.
  10. #
  11. # Licensed under the Apache License, Version 2.0 (the "License");
  12. # you may not use this file except in compliance with the License.
  13. # You may obtain a copy of the License at
  14. #
  15. # http://www.apache.org/licenses/LICENSE-2.0
  16. #
  17. # Unless required by applicable law or agreed to in writing, software
  18. # distributed under the License is distributed on an "AS IS" BASIS,
  19. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20. # See the License for the specific language governing permissions and
  21. # limitations under the License.
  22. """This module provides a pythonic, gdata-centric interface to Google Photos
  23. (a.k.a. Picasa Web Services.
  24. It is modelled after the gdata/* interfaces from the gdata-python-client
  25. project[1] by Google.
  26. You'll find the user-friendly api in photos.service. Please see the
  27. documentation or live help() system for available methods.
  28. [1]: http://gdata-python-client.googlecode.com/
  29. """
  30. __author__ = u'havard@gulldahl.no'# (H?vard Gulldahl)' #BUG: pydoc chokes on non-ascii chars in __author__
  31. __license__ = 'Apache License v2'
  32. __version__ = '$Revision: 164 $'[11:-2]
  33. import re
  34. try:
  35. from xml.etree import cElementTree as ElementTree
  36. except ImportError:
  37. try:
  38. import cElementTree as ElementTree
  39. except ImportError:
  40. try:
  41. from xml.etree import ElementTree
  42. except ImportError:
  43. from elementtree import ElementTree
  44. import atom
  45. import gdata
  46. # importing google photo submodules
  47. import gdata.media as Media, gdata.exif as Exif, gdata.geo as Geo
  48. # XML namespaces which are often used in Google Photo elements
  49. PHOTOS_NAMESPACE = 'http://schemas.google.com/photos/2007'
  50. MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/'
  51. EXIF_NAMESPACE = 'http://schemas.google.com/photos/exif/2007'
  52. OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/'
  53. GEO_NAMESPACE = 'http://www.w3.org/2003/01/geo/wgs84_pos#'
  54. GML_NAMESPACE = 'http://www.opengis.net/gml'
  55. GEORSS_NAMESPACE = 'http://www.georss.org/georss'
  56. PHEED_NAMESPACE = 'http://www.pheed.com/pheed/'
  57. BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch'
  58. class PhotosBaseElement(atom.AtomBase):
  59. """Base class for elements in the PHOTO_NAMESPACE. To add new elements,
  60. you only need to add the element tag name to self._tag
  61. """
  62. _tag = ''
  63. _namespace = PHOTOS_NAMESPACE
  64. _children = atom.AtomBase._children.copy()
  65. _attributes = atom.AtomBase._attributes.copy()
  66. def __init__(self, name=None, extension_elements=None,
  67. extension_attributes=None, text=None):
  68. self.name = name
  69. self.text = text
  70. self.extension_elements = extension_elements or []
  71. self.extension_attributes = extension_attributes or {}
  72. #def __str__(self):
  73. #return str(self.text)
  74. #def __unicode__(self):
  75. #return unicode(self.text)
  76. def __int__(self):
  77. return int(self.text)
  78. def bool(self):
  79. return self.text == 'true'
  80. class GPhotosBaseFeed(gdata.GDataFeed, gdata.LinkFinder):
  81. "Base class for all Feeds in gdata.photos"
  82. _tag = 'feed'
  83. _namespace = atom.ATOM_NAMESPACE
  84. _attributes = gdata.GDataFeed._attributes.copy()
  85. _children = gdata.GDataFeed._children.copy()
  86. # We deal with Entry elements ourselves
  87. del _children['{%s}entry' % atom.ATOM_NAMESPACE]
  88. def __init__(self, author=None, category=None, contributor=None,
  89. generator=None, icon=None, atom_id=None, link=None, logo=None,
  90. rights=None, subtitle=None, title=None, updated=None,
  91. entry=None, total_results=None, start_index=None,
  92. items_per_page=None, extension_elements=None,
  93. extension_attributes=None, text=None):
  94. gdata.GDataFeed.__init__(self, author=author, category=category,
  95. contributor=contributor, generator=generator,
  96. icon=icon, atom_id=atom_id, link=link,
  97. logo=logo, rights=rights, subtitle=subtitle,
  98. title=title, updated=updated, entry=entry,
  99. total_results=total_results,
  100. start_index=start_index,
  101. items_per_page=items_per_page,
  102. extension_elements=extension_elements,
  103. extension_attributes=extension_attributes,
  104. text=text)
  105. def kind(self):
  106. "(string) Returns the kind"
  107. try:
  108. return self.category[0].term.split('#')[1]
  109. except IndexError:
  110. return None
  111. def _feedUri(self, kind):
  112. "Convenience method to return a uri to a feed of a special kind"
  113. assert(kind in ('album', 'tag', 'photo', 'comment', 'user'))
  114. here_href = self.GetSelfLink().href
  115. if 'kind=%s' % kind in here_href:
  116. return here_href
  117. if not 'kind=' in here_href:
  118. sep = '?'
  119. if '?' in here_href: sep = '&'
  120. return here_href + "%skind=%s" % (sep, kind)
  121. rx = re.match('.*(kind=)(album|tag|photo|comment)', here_href)
  122. return here_href[:rx.end(1)] + kind + here_href[rx.end(2):]
  123. def _ConvertElementTreeToMember(self, child_tree):
  124. """Re-implementing the method from AtomBase, since we deal with
  125. Entry elements specially"""
  126. category = child_tree.find('{%s}category' % atom.ATOM_NAMESPACE)
  127. if category is None:
  128. return atom.AtomBase._ConvertElementTreeToMember(self, child_tree)
  129. namespace, kind = category.get('term').split('#')
  130. if namespace != PHOTOS_NAMESPACE:
  131. return atom.AtomBase._ConvertElementTreeToMember(self, child_tree)
  132. ## TODO: is it safe to use getattr on gdata.photos?
  133. entry_class = getattr(gdata.photos, '%sEntry' % kind.title())
  134. if not hasattr(self, 'entry') or self.entry is None:
  135. self.entry = []
  136. self.entry.append(atom._CreateClassFromElementTree(
  137. entry_class, child_tree))
  138. class GPhotosBaseEntry(gdata.GDataEntry, gdata.LinkFinder):
  139. "Base class for all Entry elements in gdata.photos"
  140. _tag = 'entry'
  141. _kind = ''
  142. _namespace = atom.ATOM_NAMESPACE
  143. _children = gdata.GDataEntry._children.copy()
  144. _attributes = gdata.GDataEntry._attributes.copy()
  145. def __init__(self, author=None, category=None, content=None,
  146. atom_id=None, link=None, published=None,
  147. title=None, updated=None,
  148. extended_property=None,
  149. extension_elements=None, extension_attributes=None, text=None):
  150. gdata.GDataEntry.__init__(self, author=author, category=category,
  151. content=content, atom_id=atom_id, link=link,
  152. published=published, title=title,
  153. updated=updated, text=text,
  154. extension_elements=extension_elements,
  155. extension_attributes=extension_attributes)
  156. self.category.append(
  157. atom.Category(scheme='http://schemas.google.com/g/2005#kind',
  158. term = 'http://schemas.google.com/photos/2007#%s' % self._kind))
  159. def kind(self):
  160. "(string) Returns the kind"
  161. try:
  162. return self.category[0].term.split('#')[1]
  163. except IndexError:
  164. return None
  165. def _feedUri(self, kind):
  166. "Convenience method to get the uri to this entry's feed of the some kind"
  167. try:
  168. href = self.GetFeedLink().href
  169. except AttributeError:
  170. return None
  171. sep = '?'
  172. if '?' in href: sep = '&'
  173. return '%s%skind=%s' % (href, sep, kind)
  174. class PhotosBaseEntry(GPhotosBaseEntry):
  175. pass
  176. class PhotosBaseFeed(GPhotosBaseFeed):
  177. pass
  178. class GPhotosBaseData(object):
  179. pass
  180. class Access(PhotosBaseElement):
  181. """The Google Photo `Access' element.
  182. The album's access level. Valid values are `public' or `private'.
  183. In documentation, access level is also referred to as `visibility.'"""
  184. _tag = 'access'
  185. def AccessFromString(xml_string):
  186. return atom.CreateClassFromXMLString(Access, xml_string)
  187. class Albumid(PhotosBaseElement):
  188. "The Google Photo `Albumid' element"
  189. _tag = 'albumid'
  190. def AlbumidFromString(xml_string):
  191. return atom.CreateClassFromXMLString(Albumid, xml_string)
  192. class BytesUsed(PhotosBaseElement):
  193. "The Google Photo `BytesUsed' element"
  194. _tag = 'bytesUsed'
  195. def BytesUsedFromString(xml_string):
  196. return atom.CreateClassFromXMLString(BytesUsed, xml_string)
  197. class Client(PhotosBaseElement):
  198. "The Google Photo `Client' element"
  199. _tag = 'client'
  200. def ClientFromString(xml_string):
  201. return atom.CreateClassFromXMLString(Client, xml_string)
  202. class Checksum(PhotosBaseElement):
  203. "The Google Photo `Checksum' element"
  204. _tag = 'checksum'
  205. def ChecksumFromString(xml_string):
  206. return atom.CreateClassFromXMLString(Checksum, xml_string)
  207. class CommentCount(PhotosBaseElement):
  208. "The Google Photo `CommentCount' element"
  209. _tag = 'commentCount'
  210. def CommentCountFromString(xml_string):
  211. return atom.CreateClassFromXMLString(CommentCount, xml_string)
  212. class CommentingEnabled(PhotosBaseElement):
  213. "The Google Photo `CommentingEnabled' element"
  214. _tag = 'commentingEnabled'
  215. def CommentingEnabledFromString(xml_string):
  216. return atom.CreateClassFromXMLString(CommentingEnabled, xml_string)
  217. class Height(PhotosBaseElement):
  218. "The Google Photo `Height' element"
  219. _tag = 'height'
  220. def HeightFromString(xml_string):
  221. return atom.CreateClassFromXMLString(Height, xml_string)
  222. class Id(PhotosBaseElement):
  223. "The Google Photo `Id' element"
  224. _tag = 'id'
  225. def IdFromString(xml_string):
  226. return atom.CreateClassFromXMLString(Id, xml_string)
  227. class Location(PhotosBaseElement):
  228. "The Google Photo `Location' element"
  229. _tag = 'location'
  230. def LocationFromString(xml_string):
  231. return atom.CreateClassFromXMLString(Location, xml_string)
  232. class MaxPhotosPerAlbum(PhotosBaseElement):
  233. "The Google Photo `MaxPhotosPerAlbum' element"
  234. _tag = 'maxPhotosPerAlbum'
  235. def MaxPhotosPerAlbumFromString(xml_string):
  236. return atom.CreateClassFromXMLString(MaxPhotosPerAlbum, xml_string)
  237. class Name(PhotosBaseElement):
  238. "The Google Photo `Name' element"
  239. _tag = 'name'
  240. def NameFromString(xml_string):
  241. return atom.CreateClassFromXMLString(Name, xml_string)
  242. class Nickname(PhotosBaseElement):
  243. "The Google Photo `Nickname' element"
  244. _tag = 'nickname'
  245. def NicknameFromString(xml_string):
  246. return atom.CreateClassFromXMLString(Nickname, xml_string)
  247. class Numphotos(PhotosBaseElement):
  248. "The Google Photo `Numphotos' element"
  249. _tag = 'numphotos'
  250. def NumphotosFromString(xml_string):
  251. return atom.CreateClassFromXMLString(Numphotos, xml_string)
  252. class Numphotosremaining(PhotosBaseElement):
  253. "The Google Photo `Numphotosremaining' element"
  254. _tag = 'numphotosremaining'
  255. def NumphotosremainingFromString(xml_string):
  256. return atom.CreateClassFromXMLString(Numphotosremaining, xml_string)
  257. class Position(PhotosBaseElement):
  258. "The Google Photo `Position' element"
  259. _tag = 'position'
  260. def PositionFromString(xml_string):
  261. return atom.CreateClassFromXMLString(Position, xml_string)
  262. class Photoid(PhotosBaseElement):
  263. "The Google Photo `Photoid' element"
  264. _tag = 'photoid'
  265. def PhotoidFromString(xml_string):
  266. return atom.CreateClassFromXMLString(Photoid, xml_string)
  267. class Quotacurrent(PhotosBaseElement):
  268. "The Google Photo `Quotacurrent' element"
  269. _tag = 'quotacurrent'
  270. def QuotacurrentFromString(xml_string):
  271. return atom.CreateClassFromXMLString(Quotacurrent, xml_string)
  272. class Quotalimit(PhotosBaseElement):
  273. "The Google Photo `Quotalimit' element"
  274. _tag = 'quotalimit'
  275. def QuotalimitFromString(xml_string):
  276. return atom.CreateClassFromXMLString(Quotalimit, xml_string)
  277. class Rotation(PhotosBaseElement):
  278. "The Google Photo `Rotation' element"
  279. _tag = 'rotation'
  280. def RotationFromString(xml_string):
  281. return atom.CreateClassFromXMLString(Rotation, xml_string)
  282. class Size(PhotosBaseElement):
  283. "The Google Photo `Size' element"
  284. _tag = 'size'
  285. def SizeFromString(xml_string):
  286. return atom.CreateClassFromXMLString(Size, xml_string)
  287. class Snippet(PhotosBaseElement):
  288. """The Google Photo `snippet' element.
  289. When searching, the snippet element will contain a
  290. string with the word you're looking for, highlighted in html markup
  291. E.g. when your query is `hafjell', this element may contain:
  292. `... here at <b>Hafjell</b>.'
  293. You'll find this element in searches -- that is, feeds that combine the
  294. `kind=photo' and `q=yoursearch' parameters in the request.
  295. See also gphoto:truncated and gphoto:snippettype.
  296. """
  297. _tag = 'snippet'
  298. def SnippetFromString(xml_string):
  299. return atom.CreateClassFromXMLString(Snippet, xml_string)
  300. class Snippettype(PhotosBaseElement):
  301. """The Google Photo `Snippettype' element
  302. When searching, this element will tell you the type of element that matches.
  303. You'll find this element in searches -- that is, feeds that combine the
  304. `kind=photo' and `q=yoursearch' parameters in the request.
  305. See also gphoto:snippet and gphoto:truncated.
  306. Possible values and their interpretation:
  307. o ALBUM_TITLE - The album title matches
  308. o PHOTO_TAGS - The match is a tag/keyword
  309. o PHOTO_DESCRIPTION - The match is in the photo's description
  310. If you discover a value not listed here, please submit a patch to update this docstring.
  311. """
  312. _tag = 'snippettype'
  313. def SnippettypeFromString(xml_string):
  314. return atom.CreateClassFromXMLString(Snippettype, xml_string)
  315. class Thumbnail(PhotosBaseElement):
  316. """The Google Photo `Thumbnail' element
  317. Used to display user's photo thumbnail (hackergotchi).
  318. (Not to be confused with the <media:thumbnail> element, which gives you
  319. small versions of the photo object.)"""
  320. _tag = 'thumbnail'
  321. def ThumbnailFromString(xml_string):
  322. return atom.CreateClassFromXMLString(Thumbnail, xml_string)
  323. class Timestamp(PhotosBaseElement):
  324. """The Google Photo `Timestamp' element
  325. Represented as the number of milliseconds since January 1st, 1970.
  326. Take a look at the convenience methods .isoformat() and .datetime():
  327. photo_epoch = Time.text # 1180294337000
  328. photo_isostring = Time.isoformat() # '2007-05-27T19:32:17.000Z'
  329. Alternatively:
  330. photo_datetime = Time.datetime() # (requires python >= 2.3)
  331. """
  332. _tag = 'timestamp'
  333. def isoformat(self):
  334. """(string) Return the timestamp as a ISO 8601 formatted string,
  335. e.g. '2007-05-27T19:32:17.000Z'
  336. """
  337. import time
  338. epoch = float(self.text)/1000
  339. return time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime(epoch))
  340. def datetime(self):
  341. """(datetime.datetime) Return the timestamp as a datetime.datetime object
  342. Requires python 2.3
  343. """
  344. import datetime
  345. epoch = float(self.text)/1000
  346. return datetime.datetime.fromtimestamp(epoch)
  347. def TimestampFromString(xml_string):
  348. return atom.CreateClassFromXMLString(Timestamp, xml_string)
  349. class Truncated(PhotosBaseElement):
  350. """The Google Photo `Truncated' element
  351. You'll find this element in searches -- that is, feeds that combine the
  352. `kind=photo' and `q=yoursearch' parameters in the request.
  353. See also gphoto:snippet and gphoto:snippettype.
  354. Possible values and their interpretation:
  355. 0 -- unknown
  356. """
  357. _tag = 'Truncated'
  358. def TruncatedFromString(xml_string):
  359. return atom.CreateClassFromXMLString(Truncated, xml_string)
  360. class User(PhotosBaseElement):
  361. "The Google Photo `User' element"
  362. _tag = 'user'
  363. def UserFromString(xml_string):
  364. return atom.CreateClassFromXMLString(User, xml_string)
  365. class Version(PhotosBaseElement):
  366. "The Google Photo `Version' element"
  367. _tag = 'version'
  368. def VersionFromString(xml_string):
  369. return atom.CreateClassFromXMLString(Version, xml_string)
  370. class Width(PhotosBaseElement):
  371. "The Google Photo `Width' element"
  372. _tag = 'width'
  373. def WidthFromString(xml_string):
  374. return atom.CreateClassFromXMLString(Width, xml_string)
  375. class Weight(PhotosBaseElement):
  376. """The Google Photo `Weight' element.
  377. The weight of the tag is the number of times the tag
  378. appears in the collection of tags currently being viewed.
  379. The default weight is 1, in which case this tags is omitted."""
  380. _tag = 'weight'
  381. def WeightFromString(xml_string):
  382. return atom.CreateClassFromXMLString(Weight, xml_string)
  383. class CommentAuthor(atom.Author):
  384. """The Atom `Author' element in CommentEntry entries is augmented to
  385. contain elements from the PHOTOS_NAMESPACE
  386. http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38
  387. """
  388. _children = atom.Author._children.copy()
  389. _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User)
  390. _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname)
  391. _children['{%s}thumbnail' % PHOTOS_NAMESPACE] = ('thumbnail', Thumbnail)
  392. def CommentAuthorFromString(xml_string):
  393. return atom.CreateClassFromXMLString(CommentAuthor, xml_string)
  394. ########################## ################################
  395. class AlbumData(object):
  396. _children = {}
  397. _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id)
  398. _children['{%s}name' % PHOTOS_NAMESPACE] = ('name', Name)
  399. _children['{%s}location' % PHOTOS_NAMESPACE] = ('location', Location)
  400. _children['{%s}access' % PHOTOS_NAMESPACE] = ('access', Access)
  401. _children['{%s}bytesUsed' % PHOTOS_NAMESPACE] = ('bytesUsed', BytesUsed)
  402. _children['{%s}timestamp' % PHOTOS_NAMESPACE] = ('timestamp', Timestamp)
  403. _children['{%s}numphotos' % PHOTOS_NAMESPACE] = ('numphotos', Numphotos)
  404. _children['{%s}numphotosremaining' % PHOTOS_NAMESPACE] = \
  405. ('numphotosremaining', Numphotosremaining)
  406. _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User)
  407. _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname)
  408. _children['{%s}commentingEnabled' % PHOTOS_NAMESPACE] = \
  409. ('commentingEnabled', CommentingEnabled)
  410. _children['{%s}commentCount' % PHOTOS_NAMESPACE] = \
  411. ('commentCount', CommentCount)
  412. ## NOTE: storing media:group as self.media, to create a self-explaining api
  413. gphoto_id = None
  414. name = None
  415. location = None
  416. access = None
  417. bytesUsed = None
  418. timestamp = None
  419. numphotos = None
  420. numphotosremaining = None
  421. user = None
  422. nickname = None
  423. commentingEnabled = None
  424. commentCount = None
  425. class AlbumEntry(GPhotosBaseEntry, AlbumData):
  426. """All metadata for a Google Photos Album
  427. Take a look at AlbumData for metadata accessible as attributes to this object.
  428. Notes:
  429. To avoid name clashes, and to create a more sensible api, some
  430. objects have names that differ from the original elements:
  431. o media:group -> self.media,
  432. o geo:where -> self.geo,
  433. o photo:id -> self.gphoto_id
  434. """
  435. _kind = 'album'
  436. _children = GPhotosBaseEntry._children.copy()
  437. _children.update(AlbumData._children.copy())
  438. # child tags only for Album entries, not feeds
  439. _children['{%s}where' % GEORSS_NAMESPACE] = ('geo', Geo.Where)
  440. _children['{%s}group' % MEDIA_NAMESPACE] = ('media', Media.Group)
  441. media = Media.Group()
  442. geo = Geo.Where()
  443. def __init__(self, author=None, category=None, content=None,
  444. atom_id=None, link=None, published=None,
  445. title=None, updated=None,
  446. #GPHOTO NAMESPACE:
  447. gphoto_id=None, name=None, location=None, access=None,
  448. timestamp=None, numphotos=None, user=None, nickname=None,
  449. commentingEnabled=None, commentCount=None, thumbnail=None,
  450. # MEDIA NAMESPACE:
  451. media=None,
  452. # GEORSS NAMESPACE:
  453. geo=None,
  454. extended_property=None,
  455. extension_elements=None, extension_attributes=None, text=None):
  456. GPhotosBaseEntry.__init__(self, author=author, category=category,
  457. content=content, atom_id=atom_id, link=link,
  458. published=published, title=title,
  459. updated=updated, text=text,
  460. extension_elements=extension_elements,
  461. extension_attributes=extension_attributes)
  462. ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id
  463. self.gphoto_id = gphoto_id
  464. self.name = name
  465. self.location = location
  466. self.access = access
  467. self.timestamp = timestamp
  468. self.numphotos = numphotos
  469. self.user = user
  470. self.nickname = nickname
  471. self.commentingEnabled = commentingEnabled
  472. self.commentCount = commentCount
  473. self.thumbnail = thumbnail
  474. self.extended_property = extended_property or []
  475. self.text = text
  476. ## NOTE: storing media:group as self.media, and geo:where as geo,
  477. ## to create a self-explaining api
  478. self.media = media or Media.Group()
  479. self.geo = geo or Geo.Where()
  480. def GetAlbumId(self):
  481. "Return the id of this album"
  482. return self.GetFeedLink().href.split('/')[-1]
  483. def GetPhotosUri(self):
  484. "(string) Return the uri to this albums feed of the PhotoEntry kind"
  485. return self._feedUri('photo')
  486. def GetCommentsUri(self):
  487. "(string) Return the uri to this albums feed of the CommentEntry kind"
  488. return self._feedUri('comment')
  489. def GetTagsUri(self):
  490. "(string) Return the uri to this albums feed of the TagEntry kind"
  491. return self._feedUri('tag')
  492. def AlbumEntryFromString(xml_string):
  493. return atom.CreateClassFromXMLString(AlbumEntry, xml_string)
  494. class AlbumFeed(GPhotosBaseFeed, AlbumData):
  495. """All metadata for a Google Photos Album, including its sub-elements
  496. This feed represents an album as the container for other objects.
  497. A Album feed contains entries of
  498. PhotoEntry, CommentEntry or TagEntry,
  499. depending on the `kind' parameter in the original query.
  500. Take a look at AlbumData for accessible attributes.
  501. """
  502. _children = GPhotosBaseFeed._children.copy()
  503. _children.update(AlbumData._children.copy())
  504. def GetPhotosUri(self):
  505. "(string) Return the uri to the same feed, but of the PhotoEntry kind"
  506. return self._feedUri('photo')
  507. def GetTagsUri(self):
  508. "(string) Return the uri to the same feed, but of the TagEntry kind"
  509. return self._feedUri('tag')
  510. def GetCommentsUri(self):
  511. "(string) Return the uri to the same feed, but of the CommentEntry kind"
  512. return self._feedUri('comment')
  513. def AlbumFeedFromString(xml_string):
  514. return atom.CreateClassFromXMLString(AlbumFeed, xml_string)
  515. class PhotoData(object):
  516. _children = {}
  517. ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id
  518. _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id)
  519. _children['{%s}albumid' % PHOTOS_NAMESPACE] = ('albumid', Albumid)
  520. _children['{%s}checksum' % PHOTOS_NAMESPACE] = ('checksum', Checksum)
  521. _children['{%s}client' % PHOTOS_NAMESPACE] = ('client', Client)
  522. _children['{%s}height' % PHOTOS_NAMESPACE] = ('height', Height)
  523. _children['{%s}position' % PHOTOS_NAMESPACE] = ('position', Position)
  524. _children['{%s}rotation' % PHOTOS_NAMESPACE] = ('rotation', Rotation)
  525. _children['{%s}size' % PHOTOS_NAMESPACE] = ('size', Size)
  526. _children['{%s}timestamp' % PHOTOS_NAMESPACE] = ('timestamp', Timestamp)
  527. _children['{%s}version' % PHOTOS_NAMESPACE] = ('version', Version)
  528. _children['{%s}width' % PHOTOS_NAMESPACE] = ('width', Width)
  529. _children['{%s}commentingEnabled' % PHOTOS_NAMESPACE] = \
  530. ('commentingEnabled', CommentingEnabled)
  531. _children['{%s}commentCount' % PHOTOS_NAMESPACE] = \
  532. ('commentCount', CommentCount)
  533. ## NOTE: storing media:group as self.media, exif:tags as self.exif, and
  534. ## geo:where as self.geo, to create a self-explaining api
  535. _children['{%s}tags' % EXIF_NAMESPACE] = ('exif', Exif.Tags)
  536. _children['{%s}where' % GEORSS_NAMESPACE] = ('geo', Geo.Where)
  537. _children['{%s}group' % MEDIA_NAMESPACE] = ('media', Media.Group)
  538. # These elements show up in search feeds
  539. _children['{%s}snippet' % PHOTOS_NAMESPACE] = ('snippet', Snippet)
  540. _children['{%s}snippettype' % PHOTOS_NAMESPACE] = ('snippettype', Snippettype)
  541. _children['{%s}truncated' % PHOTOS_NAMESPACE] = ('truncated', Truncated)
  542. gphoto_id = None
  543. albumid = None
  544. checksum = None
  545. client = None
  546. height = None
  547. position = None
  548. rotation = None
  549. size = None
  550. timestamp = None
  551. version = None
  552. width = None
  553. commentingEnabled = None
  554. commentCount = None
  555. snippet=None
  556. snippettype=None
  557. truncated=None
  558. media = Media.Group()
  559. geo = Geo.Where()
  560. tags = Exif.Tags()
  561. class PhotoEntry(GPhotosBaseEntry, PhotoData):
  562. """All metadata for a Google Photos Photo
  563. Take a look at PhotoData for metadata accessible as attributes to this object.
  564. Notes:
  565. To avoid name clashes, and to create a more sensible api, some
  566. objects have names that differ from the original elements:
  567. o media:group -> self.media,
  568. o exif:tags -> self.exif,
  569. o geo:where -> self.geo,
  570. o photo:id -> self.gphoto_id
  571. """
  572. _kind = 'photo'
  573. _children = GPhotosBaseEntry._children.copy()
  574. _children.update(PhotoData._children.copy())
  575. def __init__(self, author=None, category=None, content=None,
  576. atom_id=None, link=None, published=None,
  577. title=None, updated=None, text=None,
  578. # GPHOTO NAMESPACE:
  579. gphoto_id=None, albumid=None, checksum=None, client=None, height=None,
  580. position=None, rotation=None, size=None, timestamp=None, version=None,
  581. width=None, commentCount=None, commentingEnabled=None,
  582. # MEDIARSS NAMESPACE:
  583. media=None,
  584. # EXIF_NAMESPACE:
  585. exif=None,
  586. # GEORSS NAMESPACE:
  587. geo=None,
  588. extension_elements=None, extension_attributes=None):
  589. GPhotosBaseEntry.__init__(self, author=author, category=category,
  590. content=content,
  591. atom_id=atom_id, link=link, published=published,
  592. title=title, updated=updated, text=text,
  593. extension_elements=extension_elements,
  594. extension_attributes=extension_attributes)
  595. ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id
  596. self.gphoto_id = gphoto_id
  597. self.albumid = albumid
  598. self.checksum = checksum
  599. self.client = client
  600. self.height = height
  601. self.position = position
  602. self.rotation = rotation
  603. self.size = size
  604. self.timestamp = timestamp
  605. self.version = version
  606. self.width = width
  607. self.commentingEnabled = commentingEnabled
  608. self.commentCount = commentCount
  609. ## NOTE: storing media:group as self.media, to create a self-explaining api
  610. self.media = media or Media.Group()
  611. self.exif = exif or Exif.Tags()
  612. self.geo = geo or Geo.Where()
  613. def GetPostLink(self):
  614. "Return the uri to this photo's `POST' link (use it for updates of the object)"
  615. return self.GetFeedLink()
  616. def GetCommentsUri(self):
  617. "Return the uri to this photo's feed of CommentEntry comments"
  618. return self._feedUri('comment')
  619. def GetTagsUri(self):
  620. "Return the uri to this photo's feed of TagEntry tags"
  621. return self._feedUri('tag')
  622. def GetAlbumUri(self):
  623. """Return the uri to the AlbumEntry containing this photo"""
  624. href = self.GetSelfLink().href
  625. return href[:href.find('/photoid')]
  626. def PhotoEntryFromString(xml_string):
  627. return atom.CreateClassFromXMLString(PhotoEntry, xml_string)
  628. class PhotoFeed(GPhotosBaseFeed, PhotoData):
  629. """All metadata for a Google Photos Photo, including its sub-elements
  630. This feed represents a photo as the container for other objects.
  631. A Photo feed contains entries of
  632. CommentEntry or TagEntry,
  633. depending on the `kind' parameter in the original query.
  634. Take a look at PhotoData for metadata accessible as attributes to this object.
  635. """
  636. _children = GPhotosBaseFeed._children.copy()
  637. _children.update(PhotoData._children.copy())
  638. def GetTagsUri(self):
  639. "(string) Return the uri to the same feed, but of the TagEntry kind"
  640. return self._feedUri('tag')
  641. def GetCommentsUri(self):
  642. "(string) Return the uri to the same feed, but of the CommentEntry kind"
  643. return self._feedUri('comment')
  644. def PhotoFeedFromString(xml_string):
  645. return atom.CreateClassFromXMLString(PhotoFeed, xml_string)
  646. class TagData(GPhotosBaseData):
  647. _children = {}
  648. _children['{%s}weight' % PHOTOS_NAMESPACE] = ('weight', Weight)
  649. weight=None
  650. class TagEntry(GPhotosBaseEntry, TagData):
  651. """All metadata for a Google Photos Tag
  652. The actual tag is stored in the .title.text attribute
  653. """
  654. _kind = 'tag'
  655. _children = GPhotosBaseEntry._children.copy()
  656. _children.update(TagData._children.copy())
  657. def __init__(self, author=None, category=None, content=None,
  658. atom_id=None, link=None, published=None,
  659. title=None, updated=None,
  660. # GPHOTO NAMESPACE:
  661. weight=None,
  662. extended_property=None,
  663. extension_elements=None, extension_attributes=None, text=None):
  664. GPhotosBaseEntry.__init__(self, author=author, category=category,
  665. content=content,
  666. atom_id=atom_id, link=link, published=published,
  667. title=title, updated=updated, text=text,
  668. extension_elements=extension_elements,
  669. extension_attributes=extension_attributes)
  670. self.weight = weight
  671. def GetAlbumUri(self):
  672. """Return the uri to the AlbumEntry containing this tag"""
  673. href = self.GetSelfLink().href
  674. pos = href.find('/photoid')
  675. if pos == -1:
  676. return None
  677. return href[:pos]
  678. def GetPhotoUri(self):
  679. """Return the uri to the PhotoEntry containing this tag"""
  680. href = self.GetSelfLink().href
  681. pos = href.find('/tag')
  682. if pos == -1:
  683. return None
  684. return href[:pos]
  685. def TagEntryFromString(xml_string):
  686. return atom.CreateClassFromXMLString(TagEntry, xml_string)
  687. class TagFeed(GPhotosBaseFeed, TagData):
  688. """All metadata for a Google Photos Tag, including its sub-elements"""
  689. _children = GPhotosBaseFeed._children.copy()
  690. _children.update(TagData._children.copy())
  691. def TagFeedFromString(xml_string):
  692. return atom.CreateClassFromXMLString(TagFeed, xml_string)
  693. class CommentData(GPhotosBaseData):
  694. _children = {}
  695. ## NOTE: storing photo:id as self.gphoto_id, to avoid name clash with atom:id
  696. _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id)
  697. _children['{%s}albumid' % PHOTOS_NAMESPACE] = ('albumid', Albumid)
  698. _children['{%s}photoid' % PHOTOS_NAMESPACE] = ('photoid', Photoid)
  699. _children['{%s}author' % atom.ATOM_NAMESPACE] = ('author', [CommentAuthor,])
  700. gphoto_id=None
  701. albumid=None
  702. photoid=None
  703. author=None
  704. class CommentEntry(GPhotosBaseEntry, CommentData):
  705. """All metadata for a Google Photos Comment
  706. The comment is stored in the .content.text attribute,
  707. with a content type in .content.type.
  708. """
  709. _kind = 'comment'
  710. _children = GPhotosBaseEntry._children.copy()
  711. _children.update(CommentData._children.copy())
  712. def __init__(self, author=None, category=None, content=None,
  713. atom_id=None, link=None, published=None,
  714. title=None, updated=None,
  715. # GPHOTO NAMESPACE:
  716. gphoto_id=None, albumid=None, photoid=None,
  717. extended_property=None,
  718. extension_elements=None, extension_attributes=None, text=None):
  719. GPhotosBaseEntry.__init__(self, author=author, category=category,
  720. content=content,
  721. atom_id=atom_id, link=link, published=published,
  722. title=title, updated=updated,
  723. extension_elements=extension_elements,
  724. extension_attributes=extension_attributes,
  725. text=text)
  726. self.gphoto_id = gphoto_id
  727. self.albumid = albumid
  728. self.photoid = photoid
  729. def GetCommentId(self):
  730. """Return the globally unique id of this comment"""
  731. return self.GetSelfLink().href.split('/')[-1]
  732. def GetAlbumUri(self):
  733. """Return the uri to the AlbumEntry containing this comment"""
  734. href = self.GetSelfLink().href
  735. return href[:href.find('/photoid')]
  736. def GetPhotoUri(self):
  737. """Return the uri to the PhotoEntry containing this comment"""
  738. href = self.GetSelfLink().href
  739. return href[:href.find('/commentid')]
  740. def CommentEntryFromString(xml_string):
  741. return atom.CreateClassFromXMLString(CommentEntry, xml_string)
  742. class CommentFeed(GPhotosBaseFeed, CommentData):
  743. """All metadata for a Google Photos Comment, including its sub-elements"""
  744. _children = GPhotosBaseFeed._children.copy()
  745. _children.update(CommentData._children.copy())
  746. def CommentFeedFromString(xml_string):
  747. return atom.CreateClassFromXMLString(CommentFeed, xml_string)
  748. class UserData(GPhotosBaseData):
  749. _children = {}
  750. _children['{%s}maxPhotosPerAlbum' % PHOTOS_NAMESPACE] = ('maxPhotosPerAlbum', MaxPhotosPerAlbum)
  751. _children['{%s}nickname' % PHOTOS_NAMESPACE] = ('nickname', Nickname)
  752. _children['{%s}quotalimit' % PHOTOS_NAMESPACE] = ('quotalimit', Quotalimit)
  753. _children['{%s}quotacurrent' % PHOTOS_NAMESPACE] = ('quotacurrent', Quotacurrent)
  754. _children['{%s}thumbnail' % PHOTOS_NAMESPACE] = ('thumbnail', Thumbnail)
  755. _children['{%s}user' % PHOTOS_NAMESPACE] = ('user', User)
  756. _children['{%s}id' % PHOTOS_NAMESPACE] = ('gphoto_id', Id)
  757. maxPhotosPerAlbum=None
  758. nickname=None
  759. quotalimit=None
  760. quotacurrent=None
  761. thumbnail=None
  762. user=None
  763. gphoto_id=None
  764. class UserEntry(GPhotosBaseEntry, UserData):
  765. """All metadata for a Google Photos User
  766. This entry represents an album owner and all appropriate metadata.
  767. Take a look at at the attributes of the UserData for metadata available.
  768. """
  769. _children = GPhotosBaseEntry._children.copy()
  770. _children.update(UserData._children.copy())
  771. _kind = 'user'
  772. def __init__(self, author=None, category=None, content=None,
  773. atom_id=None, link=None, published=None,
  774. title=None, updated=None,
  775. # GPHOTO NAMESPACE:
  776. gphoto_id=None, maxPhotosPerAlbum=None, nickname=None, quotalimit=None,
  777. quotacurrent=None, thumbnail=None, user=None,
  778. extended_property=None,
  779. extension_elements=None, extension_attributes=None, text=None):
  780. GPhotosBaseEntry.__init__(self, author=author, category=category,
  781. content=content,
  782. atom_id=atom_id, link=link, published=published,
  783. title=title, updated=updated,
  784. extension_elements=extension_elements,
  785. extension_attributes=extension_attributes,
  786. text=text)
  787. self.gphoto_id=gphoto_id
  788. self.maxPhotosPerAlbum=maxPhotosPerAlbum
  789. self.nickname=nickname
  790. self.quotalimit=quotalimit
  791. self.quotacurrent=quotacurrent
  792. self.thumbnail=thumbnail
  793. self.user=user
  794. def GetAlbumsUri(self):
  795. "(string) Return the uri to this user's feed of the AlbumEntry kind"
  796. return self._feedUri('album')
  797. def GetPhotosUri(self):
  798. "(string) Return the uri to this user's feed of the PhotoEntry kind"
  799. return self._feedUri('photo')
  800. def GetCommentsUri(self):
  801. "(string) Return the uri to this user's feed of the CommentEntry kind"
  802. return self._feedUri('comment')
  803. def GetTagsUri(self):
  804. "(string) Return the uri to this user's feed of the TagEntry kind"
  805. return self._feedUri('tag')
  806. def UserEntryFromString(xml_string):
  807. return atom.CreateClassFromXMLString(UserEntry, xml_string)
  808. class UserFeed(GPhotosBaseFeed, UserData):
  809. """Feed for a User in the google photos api.
  810. This feed represents a user as the container for other objects.
  811. A User feed contains entries of
  812. AlbumEntry, PhotoEntry, CommentEntry, UserEntry or TagEntry,
  813. depending on the `kind' parameter in the original query.
  814. The user feed itself also contains all of the metadata available
  815. as part of a UserData object."""
  816. _children = GPhotosBaseFeed._children.copy()
  817. _children.update(UserData._children.copy())
  818. def GetAlbumsUri(self):
  819. """Get the uri to this feed, but with entries of the AlbumEntry kind."""
  820. return self._feedUri('album')
  821. def GetTagsUri(self):
  822. """Get the uri to this feed, but with entries of the TagEntry kind."""
  823. return self._feedUri('tag')
  824. def GetPhotosUri(self):
  825. """Get the uri to this feed, but with entries of the PhotosEntry kind."""
  826. return self._feedUri('photo')
  827. def GetCommentsUri(self):
  828. """Get the uri to this feed, but with entries of the CommentsEntry kind."""
  829. return self._feedUri('comment')
  830. def UserFeedFromString(xml_string):
  831. return atom.CreateClassFromXMLString(UserFeed, xml_string)
  832. def AnyFeedFromString(xml_string):
  833. """Creates an instance of the appropriate feed class from the
  834. xml string contents.
  835. Args:
  836. xml_string: str A string which contains valid XML. The root element
  837. of the XML string should match the tag and namespace of the desired
  838. class.
  839. Returns:
  840. An instance of the target class with members assigned according to the
  841. contents of the XML - or a basic gdata.GDataFeed instance if it is
  842. impossible to determine the appropriate class (look for extra elements
  843. in GDataFeed's .FindExtensions() and extension_elements[] ).
  844. """
  845. tree = ElementTree.fromstring(xml_string)
  846. category = tree.find('{%s}category' % atom.ATOM_NAMESPACE)
  847. if category is None:
  848. # TODO: is this the best way to handle this?
  849. return atom._CreateClassFromElementTree(GPhotosBaseFeed, tree)
  850. namespace, kind = category.get('term').split('#')
  851. if namespace != PHOTOS_NAMESPACE:
  852. # TODO: is this the best way to handle this?
  853. return atom._CreateClassFromElementTree(GPhotosBaseFeed, tree)
  854. ## TODO: is getattr safe this way?
  855. feed_class = getattr(gdata.photos, '%sFeed' % kind.title())
  856. return atom._CreateClassFromElementTree(feed_class, tree)
  857. def AnyEntryFromString(xml_string):
  858. """Creates an instance of the appropriate entry class from the
  859. xml string contents.
  860. Args:
  861. xml_string: str A string which contains valid XML. The root element
  862. of the XML string should match the tag and namespace of the desired
  863. class.
  864. Returns:
  865. An instance of the target class with members assigned according to the
  866. contents of the XML - or a basic gdata.GDataEndry instance if it is
  867. impossible to determine the appropriate class (look for extra elements
  868. in GDataEntry's .FindExtensions() and extension_elements[] ).
  869. """
  870. tree = ElementTree.fromstring(xml_string)
  871. category = tree.find('{%s}category' % atom.ATOM_NAMESPACE)
  872. if category is None:
  873. # TODO: is this the best way to handle this?
  874. return atom._CreateClassFromElementTree(GPhotosBaseEntry, tree)
  875. namespace, kind = category.get('term').split('#')
  876. if namespace != PHOTOS_NAMESPACE:
  877. # TODO: is this the best way to handle this?
  878. return atom._CreateClassFromElementTree(GPhotosBaseEntry, tree)
  879. ## TODO: is getattr safe this way?
  880. feed_class = getattr(gdata.photos, '%sEntry' % kind.title())
  881. return atom._CreateClassFromElementTree(feed_class, tree)