/atom/__init__.py

http://radioappz.googlecode.com/ · Python · 1484 lines · 1191 code · 72 blank · 221 comment · 97 complexity · ef8e16177c6dfb17146d802edfceafe2 MD5 · raw file

  1. #!/usr/bin/python
  2. #
  3. # Copyright (C) 2006 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Contains classes representing Atom elements.
  17. Module objective: provide data classes for Atom constructs. These classes hide
  18. the XML-ness of Atom and provide a set of native Python classes to interact
  19. with.
  20. Conversions to and from XML should only be necessary when the Atom classes
  21. "touch the wire" and are sent over HTTP. For this reason this module
  22. provides methods and functions to convert Atom classes to and from strings.
  23. For more information on the Atom data model, see RFC 4287
  24. (http://www.ietf.org/rfc/rfc4287.txt)
  25. AtomBase: A foundation class on which Atom classes are built. It
  26. handles the parsing of attributes and children which are common to all
  27. Atom classes. By default, the AtomBase class translates all XML child
  28. nodes into ExtensionElements.
  29. ExtensionElement: Atom allows Atom objects to contain XML which is not part
  30. of the Atom specification, these are called extension elements. If a
  31. classes parser encounters an unexpected XML construct, it is translated
  32. into an ExtensionElement instance. ExtensionElement is designed to fully
  33. capture the information in the XML. Child nodes in an XML extension are
  34. turned into ExtensionElements as well.
  35. """
  36. __author__ = 'api.jscudder (Jeffrey Scudder)'
  37. try:
  38. from xml.etree import cElementTree as ElementTree
  39. except ImportError:
  40. try:
  41. import cElementTree as ElementTree
  42. except ImportError:
  43. try:
  44. from xml.etree import ElementTree
  45. except ImportError:
  46. from elementtree import ElementTree
  47. import warnings
  48. # XML namespaces which are often used in Atom entities.
  49. ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'
  50. ELEMENT_TEMPLATE = '{http://www.w3.org/2005/Atom}%s'
  51. APP_NAMESPACE = 'http://purl.org/atom/app#'
  52. APP_TEMPLATE = '{http://purl.org/atom/app#}%s'
  53. # This encoding is used for converting strings before translating the XML
  54. # into an object.
  55. XML_STRING_ENCODING = 'utf-8'
  56. # The desired string encoding for object members. set or monkey-patch to
  57. # unicode if you want object members to be Python unicode strings, instead of
  58. # encoded strings
  59. MEMBER_STRING_ENCODING = 'utf-8'
  60. #MEMBER_STRING_ENCODING = unicode
  61. # If True, all methods which are exclusive to v1 will raise a
  62. # DeprecationWarning
  63. ENABLE_V1_WARNINGS = False
  64. def v1_deprecated(warning=None):
  65. """Shows a warning if ENABLE_V1_WARNINGS is True.
  66. Function decorator used to mark methods used in v1 classes which
  67. may be removed in future versions of the library.
  68. """
  69. warning = warning or ''
  70. # This closure is what is returned from the deprecated function.
  71. def mark_deprecated(f):
  72. # The deprecated_function wraps the actual call to f.
  73. def optional_warn_function(*args, **kwargs):
  74. if ENABLE_V1_WARNINGS:
  75. warnings.warn(warning, DeprecationWarning, stacklevel=2)
  76. return f(*args, **kwargs)
  77. # Preserve the original name to avoid masking all decorated functions as
  78. # 'deprecated_function'
  79. try:
  80. optional_warn_function.func_name = f.func_name
  81. except TypeError:
  82. pass # In Python2.3 we can't set the func_name
  83. return optional_warn_function
  84. return mark_deprecated
  85. def CreateClassFromXMLString(target_class, xml_string, string_encoding=None):
  86. """Creates an instance of the target class from the string contents.
  87. Args:
  88. target_class: class The class which will be instantiated and populated
  89. with the contents of the XML. This class must have a _tag and a
  90. _namespace class variable.
  91. xml_string: str A string which contains valid XML. The root element
  92. of the XML string should match the tag and namespace of the desired
  93. class.
  94. string_encoding: str The character encoding which the xml_string should
  95. be converted to before it is interpreted and translated into
  96. objects. The default is None in which case the string encoding
  97. is not changed.
  98. Returns:
  99. An instance of the target class with members assigned according to the
  100. contents of the XML - or None if the root XML tag and namespace did not
  101. match those of the target class.
  102. """
  103. encoding = string_encoding or XML_STRING_ENCODING
  104. if encoding and isinstance(xml_string, unicode):
  105. xml_string = xml_string.encode(encoding)
  106. tree = ElementTree.fromstring(xml_string)
  107. return _CreateClassFromElementTree(target_class, tree)
  108. CreateClassFromXMLString = v1_deprecated(
  109. 'Please use atom.core.parse with atom.data classes instead.')(
  110. CreateClassFromXMLString)
  111. def _CreateClassFromElementTree(target_class, tree, namespace=None, tag=None):
  112. """Instantiates the class and populates members according to the tree.
  113. Note: Only use this function with classes that have _namespace and _tag
  114. class members.
  115. Args:
  116. target_class: class The class which will be instantiated and populated
  117. with the contents of the XML.
  118. tree: ElementTree An element tree whose contents will be converted into
  119. members of the new target_class instance.
  120. namespace: str (optional) The namespace which the XML tree's root node must
  121. match. If omitted, the namespace defaults to the _namespace of the
  122. target class.
  123. tag: str (optional) The tag which the XML tree's root node must match. If
  124. omitted, the tag defaults to the _tag class member of the target
  125. class.
  126. Returns:
  127. An instance of the target class - or None if the tag and namespace of
  128. the XML tree's root node did not match the desired namespace and tag.
  129. """
  130. if namespace is None:
  131. namespace = target_class._namespace
  132. if tag is None:
  133. tag = target_class._tag
  134. if tree.tag == '{%s}%s' % (namespace, tag):
  135. target = target_class()
  136. target._HarvestElementTree(tree)
  137. return target
  138. else:
  139. return None
  140. class ExtensionContainer(object):
  141. def __init__(self, extension_elements=None, extension_attributes=None,
  142. text=None):
  143. self.extension_elements = extension_elements or []
  144. self.extension_attributes = extension_attributes or {}
  145. self.text = text
  146. __init__ = v1_deprecated(
  147. 'Please use data model classes in atom.data instead.')(
  148. __init__)
  149. # Three methods to create an object from an ElementTree
  150. def _HarvestElementTree(self, tree):
  151. # Fill in the instance members from the contents of the XML tree.
  152. for child in tree:
  153. self._ConvertElementTreeToMember(child)
  154. for attribute, value in tree.attrib.iteritems():
  155. self._ConvertElementAttributeToMember(attribute, value)
  156. # Encode the text string according to the desired encoding type. (UTF-8)
  157. if tree.text:
  158. if MEMBER_STRING_ENCODING is unicode:
  159. self.text = tree.text
  160. else:
  161. self.text = tree.text.encode(MEMBER_STRING_ENCODING)
  162. def _ConvertElementTreeToMember(self, child_tree, current_class=None):
  163. self.extension_elements.append(_ExtensionElementFromElementTree(
  164. child_tree))
  165. def _ConvertElementAttributeToMember(self, attribute, value):
  166. # Encode the attribute value's string with the desired type Default UTF-8
  167. if value:
  168. if MEMBER_STRING_ENCODING is unicode:
  169. self.extension_attributes[attribute] = value
  170. else:
  171. self.extension_attributes[attribute] = value.encode(
  172. MEMBER_STRING_ENCODING)
  173. # One method to create an ElementTree from an object
  174. def _AddMembersToElementTree(self, tree):
  175. for child in self.extension_elements:
  176. child._BecomeChildElement(tree)
  177. for attribute, value in self.extension_attributes.iteritems():
  178. if value:
  179. if isinstance(value, unicode) or MEMBER_STRING_ENCODING is unicode:
  180. tree.attrib[attribute] = value
  181. else:
  182. # Decode the value from the desired encoding (default UTF-8).
  183. tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING)
  184. if self.text:
  185. if isinstance(self.text, unicode) or MEMBER_STRING_ENCODING is unicode:
  186. tree.text = self.text
  187. else:
  188. tree.text = self.text.decode(MEMBER_STRING_ENCODING)
  189. def FindExtensions(self, tag=None, namespace=None):
  190. """Searches extension elements for child nodes with the desired name.
  191. Returns a list of extension elements within this object whose tag
  192. and/or namespace match those passed in. To find all extensions in
  193. a particular namespace, specify the namespace but not the tag name.
  194. If you specify only the tag, the result list may contain extension
  195. elements in multiple namespaces.
  196. Args:
  197. tag: str (optional) The desired tag
  198. namespace: str (optional) The desired namespace
  199. Returns:
  200. A list of elements whose tag and/or namespace match the parameters
  201. values
  202. """
  203. results = []
  204. if tag and namespace:
  205. for element in self.extension_elements:
  206. if element.tag == tag and element.namespace == namespace:
  207. results.append(element)
  208. elif tag and not namespace:
  209. for element in self.extension_elements:
  210. if element.tag == tag:
  211. results.append(element)
  212. elif namespace and not tag:
  213. for element in self.extension_elements:
  214. if element.namespace == namespace:
  215. results.append(element)
  216. else:
  217. for element in self.extension_elements:
  218. results.append(element)
  219. return results
  220. class AtomBase(ExtensionContainer):
  221. _children = {}
  222. _attributes = {}
  223. def __init__(self, extension_elements=None, extension_attributes=None,
  224. text=None):
  225. self.extension_elements = extension_elements or []
  226. self.extension_attributes = extension_attributes or {}
  227. self.text = text
  228. __init__ = v1_deprecated(
  229. 'Please use data model classes in atom.data instead.')(
  230. __init__)
  231. def _ConvertElementTreeToMember(self, child_tree):
  232. # Find the element's tag in this class's list of child members
  233. if self.__class__._children.has_key(child_tree.tag):
  234. member_name = self.__class__._children[child_tree.tag][0]
  235. member_class = self.__class__._children[child_tree.tag][1]
  236. # If the class member is supposed to contain a list, make sure the
  237. # matching member is set to a list, then append the new member
  238. # instance to the list.
  239. if isinstance(member_class, list):
  240. if getattr(self, member_name) is None:
  241. setattr(self, member_name, [])
  242. getattr(self, member_name).append(_CreateClassFromElementTree(
  243. member_class[0], child_tree))
  244. else:
  245. setattr(self, member_name,
  246. _CreateClassFromElementTree(member_class, child_tree))
  247. else:
  248. ExtensionContainer._ConvertElementTreeToMember(self, child_tree)
  249. def _ConvertElementAttributeToMember(self, attribute, value):
  250. # Find the attribute in this class's list of attributes.
  251. if self.__class__._attributes.has_key(attribute):
  252. # Find the member of this class which corresponds to the XML attribute
  253. # (lookup in current_class._attributes) and set this member to the
  254. # desired value (using self.__dict__).
  255. if value:
  256. # Encode the string to capture non-ascii characters (default UTF-8)
  257. if MEMBER_STRING_ENCODING is unicode:
  258. setattr(self, self.__class__._attributes[attribute], value)
  259. else:
  260. setattr(self, self.__class__._attributes[attribute],
  261. value.encode(MEMBER_STRING_ENCODING))
  262. else:
  263. ExtensionContainer._ConvertElementAttributeToMember(
  264. self, attribute, value)
  265. # Three methods to create an ElementTree from an object
  266. def _AddMembersToElementTree(self, tree):
  267. # Convert the members of this class which are XML child nodes.
  268. # This uses the class's _children dictionary to find the members which
  269. # should become XML child nodes.
  270. member_node_names = [values[0] for tag, values in
  271. self.__class__._children.iteritems()]
  272. for member_name in member_node_names:
  273. member = getattr(self, member_name)
  274. if member is None:
  275. pass
  276. elif isinstance(member, list):
  277. for instance in member:
  278. instance._BecomeChildElement(tree)
  279. else:
  280. member._BecomeChildElement(tree)
  281. # Convert the members of this class which are XML attributes.
  282. for xml_attribute, member_name in self.__class__._attributes.iteritems():
  283. member = getattr(self, member_name)
  284. if member is not None:
  285. if isinstance(member, unicode) or MEMBER_STRING_ENCODING is unicode:
  286. tree.attrib[xml_attribute] = member
  287. else:
  288. tree.attrib[xml_attribute] = member.decode(MEMBER_STRING_ENCODING)
  289. # Lastly, call the ExtensionContainers's _AddMembersToElementTree to
  290. # convert any extension attributes.
  291. ExtensionContainer._AddMembersToElementTree(self, tree)
  292. def _BecomeChildElement(self, tree):
  293. """
  294. Note: Only for use with classes that have a _tag and _namespace class
  295. member. It is in AtomBase so that it can be inherited but it should
  296. not be called on instances of AtomBase.
  297. """
  298. new_child = ElementTree.Element('')
  299. tree.append(new_child)
  300. new_child.tag = '{%s}%s' % (self.__class__._namespace,
  301. self.__class__._tag)
  302. self._AddMembersToElementTree(new_child)
  303. def _ToElementTree(self):
  304. """
  305. Note, this method is designed to be used only with classes that have a
  306. _tag and _namespace. It is placed in AtomBase for inheritance but should
  307. not be called on this class.
  308. """
  309. new_tree = ElementTree.Element('{%s}%s' % (self.__class__._namespace,
  310. self.__class__._tag))
  311. self._AddMembersToElementTree(new_tree)
  312. return new_tree
  313. def ToString(self, string_encoding='UTF-8'):
  314. """Converts the Atom object to a string containing XML."""
  315. return ElementTree.tostring(self._ToElementTree(), encoding=string_encoding)
  316. def __str__(self):
  317. return self.ToString()
  318. class Name(AtomBase):
  319. """The atom:name element"""
  320. _tag = 'name'
  321. _namespace = ATOM_NAMESPACE
  322. _children = AtomBase._children.copy()
  323. _attributes = AtomBase._attributes.copy()
  324. def __init__(self, text=None, extension_elements=None,
  325. extension_attributes=None):
  326. """Constructor for Name
  327. Args:
  328. text: str The text data in the this element
  329. extension_elements: list A list of ExtensionElement instances
  330. extension_attributes: dict A dictionary of attribute value string pairs
  331. """
  332. self.text = text
  333. self.extension_elements = extension_elements or []
  334. self.extension_attributes = extension_attributes or {}
  335. def NameFromString(xml_string):
  336. return CreateClassFromXMLString(Name, xml_string)
  337. class Email(AtomBase):
  338. """The atom:email element"""
  339. _tag = 'email'
  340. _namespace = ATOM_NAMESPACE
  341. _children = AtomBase._children.copy()
  342. _attributes = AtomBase._attributes.copy()
  343. def __init__(self, text=None, extension_elements=None,
  344. extension_attributes=None):
  345. """Constructor for Email
  346. Args:
  347. extension_elements: list A list of ExtensionElement instances
  348. extension_attributes: dict A dictionary of attribute value string pairs
  349. text: str The text data in the this element
  350. """
  351. self.text = text
  352. self.extension_elements = extension_elements or []
  353. self.extension_attributes = extension_attributes or {}
  354. def EmailFromString(xml_string):
  355. return CreateClassFromXMLString(Email, xml_string)
  356. class Uri(AtomBase):
  357. """The atom:uri element"""
  358. _tag = 'uri'
  359. _namespace = ATOM_NAMESPACE
  360. _children = AtomBase._children.copy()
  361. _attributes = AtomBase._attributes.copy()
  362. def __init__(self, text=None, extension_elements=None,
  363. extension_attributes=None):
  364. """Constructor for Uri
  365. Args:
  366. extension_elements: list A list of ExtensionElement instances
  367. extension_attributes: dict A dictionary of attribute value string pairs
  368. text: str The text data in the this element
  369. """
  370. self.text = text
  371. self.extension_elements = extension_elements or []
  372. self.extension_attributes = extension_attributes or {}
  373. def UriFromString(xml_string):
  374. return CreateClassFromXMLString(Uri, xml_string)
  375. class Person(AtomBase):
  376. """A foundation class from which atom:author and atom:contributor extend.
  377. A person contains information like name, email address, and web page URI for
  378. an author or contributor to an Atom feed.
  379. """
  380. _children = AtomBase._children.copy()
  381. _attributes = AtomBase._attributes.copy()
  382. _children['{%s}name' % (ATOM_NAMESPACE)] = ('name', Name)
  383. _children['{%s}email' % (ATOM_NAMESPACE)] = ('email', Email)
  384. _children['{%s}uri' % (ATOM_NAMESPACE)] = ('uri', Uri)
  385. def __init__(self, name=None, email=None, uri=None,
  386. extension_elements=None, extension_attributes=None, text=None):
  387. """Foundation from which author and contributor are derived.
  388. The constructor is provided for illustrative purposes, you should not
  389. need to instantiate a Person.
  390. Args:
  391. name: Name The person's name
  392. email: Email The person's email address
  393. uri: Uri The URI of the person's webpage
  394. extension_elements: list A list of ExtensionElement instances which are
  395. children of this element.
  396. extension_attributes: dict A dictionary of strings which are the values
  397. for additional XML attributes of this element.
  398. text: String The text contents of the element. This is the contents
  399. of the Entry's XML text node. (Example: <foo>This is the text</foo>)
  400. """
  401. self.name = name
  402. self.email = email
  403. self.uri = uri
  404. self.extension_elements = extension_elements or []
  405. self.extension_attributes = extension_attributes or {}
  406. self.text = text
  407. class Author(Person):
  408. """The atom:author element
  409. An author is a required element in Feed.
  410. """
  411. _tag = 'author'
  412. _namespace = ATOM_NAMESPACE
  413. _children = Person._children.copy()
  414. _attributes = Person._attributes.copy()
  415. #_children = {}
  416. #_attributes = {}
  417. def __init__(self, name=None, email=None, uri=None,
  418. extension_elements=None, extension_attributes=None, text=None):
  419. """Constructor for Author
  420. Args:
  421. name: Name
  422. email: Email
  423. uri: Uri
  424. extension_elements: list A list of ExtensionElement instances
  425. extension_attributes: dict A dictionary of attribute value string pairs
  426. text: str The text data in the this element
  427. """
  428. self.name = name
  429. self.email = email
  430. self.uri = uri
  431. self.extension_elements = extension_elements or []
  432. self.extension_attributes = extension_attributes or {}
  433. self.text = text
  434. def AuthorFromString(xml_string):
  435. return CreateClassFromXMLString(Author, xml_string)
  436. class Contributor(Person):
  437. """The atom:contributor element"""
  438. _tag = 'contributor'
  439. _namespace = ATOM_NAMESPACE
  440. _children = Person._children.copy()
  441. _attributes = Person._attributes.copy()
  442. def __init__(self, name=None, email=None, uri=None,
  443. extension_elements=None, extension_attributes=None, text=None):
  444. """Constructor for Contributor
  445. Args:
  446. name: Name
  447. email: Email
  448. uri: Uri
  449. extension_elements: list A list of ExtensionElement instances
  450. extension_attributes: dict A dictionary of attribute value string pairs
  451. text: str The text data in the this element
  452. """
  453. self.name = name
  454. self.email = email
  455. self.uri = uri
  456. self.extension_elements = extension_elements or []
  457. self.extension_attributes = extension_attributes or {}
  458. self.text = text
  459. def ContributorFromString(xml_string):
  460. return CreateClassFromXMLString(Contributor, xml_string)
  461. class Link(AtomBase):
  462. """The atom:link element"""
  463. _tag = 'link'
  464. _namespace = ATOM_NAMESPACE
  465. _children = AtomBase._children.copy()
  466. _attributes = AtomBase._attributes.copy()
  467. _attributes['rel'] = 'rel'
  468. _attributes['href'] = 'href'
  469. _attributes['type'] = 'type'
  470. _attributes['title'] = 'title'
  471. _attributes['length'] = 'length'
  472. _attributes['hreflang'] = 'hreflang'
  473. def __init__(self, href=None, rel=None, link_type=None, hreflang=None,
  474. title=None, length=None, text=None, extension_elements=None,
  475. extension_attributes=None):
  476. """Constructor for Link
  477. Args:
  478. href: string The href attribute of the link
  479. rel: string
  480. type: string
  481. hreflang: string The language for the href
  482. title: string
  483. length: string The length of the href's destination
  484. extension_elements: list A list of ExtensionElement instances
  485. extension_attributes: dict A dictionary of attribute value string pairs
  486. text: str The text data in the this element
  487. """
  488. self.href = href
  489. self.rel = rel
  490. self.type = link_type
  491. self.hreflang = hreflang
  492. self.title = title
  493. self.length = length
  494. self.text = text
  495. self.extension_elements = extension_elements or []
  496. self.extension_attributes = extension_attributes or {}
  497. def LinkFromString(xml_string):
  498. return CreateClassFromXMLString(Link, xml_string)
  499. class Generator(AtomBase):
  500. """The atom:generator element"""
  501. _tag = 'generator'
  502. _namespace = ATOM_NAMESPACE
  503. _children = AtomBase._children.copy()
  504. _attributes = AtomBase._attributes.copy()
  505. _attributes['uri'] = 'uri'
  506. _attributes['version'] = 'version'
  507. def __init__(self, uri=None, version=None, text=None,
  508. extension_elements=None, extension_attributes=None):
  509. """Constructor for Generator
  510. Args:
  511. uri: string
  512. version: string
  513. text: str The text data in the this element
  514. extension_elements: list A list of ExtensionElement instances
  515. extension_attributes: dict A dictionary of attribute value string pairs
  516. """
  517. self.uri = uri
  518. self.version = version
  519. self.text = text
  520. self.extension_elements = extension_elements or []
  521. self.extension_attributes = extension_attributes or {}
  522. def GeneratorFromString(xml_string):
  523. return CreateClassFromXMLString(Generator, xml_string)
  524. class Text(AtomBase):
  525. """A foundation class from which atom:title, summary, etc. extend.
  526. This class should never be instantiated.
  527. """
  528. _children = AtomBase._children.copy()
  529. _attributes = AtomBase._attributes.copy()
  530. _attributes['type'] = 'type'
  531. def __init__(self, text_type=None, text=None, extension_elements=None,
  532. extension_attributes=None):
  533. """Constructor for Text
  534. Args:
  535. text_type: string
  536. text: str The text data in the this element
  537. extension_elements: list A list of ExtensionElement instances
  538. extension_attributes: dict A dictionary of attribute value string pairs
  539. """
  540. self.type = text_type
  541. self.text = text
  542. self.extension_elements = extension_elements or []
  543. self.extension_attributes = extension_attributes or {}
  544. class Title(Text):
  545. """The atom:title element"""
  546. _tag = 'title'
  547. _namespace = ATOM_NAMESPACE
  548. _children = Text._children.copy()
  549. _attributes = Text._attributes.copy()
  550. def __init__(self, title_type=None, text=None, extension_elements=None,
  551. extension_attributes=None):
  552. """Constructor for Title
  553. Args:
  554. title_type: string
  555. text: str The text data in the this element
  556. extension_elements: list A list of ExtensionElement instances
  557. extension_attributes: dict A dictionary of attribute value string pairs
  558. """
  559. self.type = title_type
  560. self.text = text
  561. self.extension_elements = extension_elements or []
  562. self.extension_attributes = extension_attributes or {}
  563. def TitleFromString(xml_string):
  564. return CreateClassFromXMLString(Title, xml_string)
  565. class Subtitle(Text):
  566. """The atom:subtitle element"""
  567. _tag = 'subtitle'
  568. _namespace = ATOM_NAMESPACE
  569. _children = Text._children.copy()
  570. _attributes = Text._attributes.copy()
  571. def __init__(self, subtitle_type=None, text=None, extension_elements=None,
  572. extension_attributes=None):
  573. """Constructor for Subtitle
  574. Args:
  575. subtitle_type: string
  576. text: str The text data in the this element
  577. extension_elements: list A list of ExtensionElement instances
  578. extension_attributes: dict A dictionary of attribute value string pairs
  579. """
  580. self.type = subtitle_type
  581. self.text = text
  582. self.extension_elements = extension_elements or []
  583. self.extension_attributes = extension_attributes or {}
  584. def SubtitleFromString(xml_string):
  585. return CreateClassFromXMLString(Subtitle, xml_string)
  586. class Rights(Text):
  587. """The atom:rights element"""
  588. _tag = 'rights'
  589. _namespace = ATOM_NAMESPACE
  590. _children = Text._children.copy()
  591. _attributes = Text._attributes.copy()
  592. def __init__(self, rights_type=None, text=None, extension_elements=None,
  593. extension_attributes=None):
  594. """Constructor for Rights
  595. Args:
  596. rights_type: string
  597. text: str The text data in the this element
  598. extension_elements: list A list of ExtensionElement instances
  599. extension_attributes: dict A dictionary of attribute value string pairs
  600. """
  601. self.type = rights_type
  602. self.text = text
  603. self.extension_elements = extension_elements or []
  604. self.extension_attributes = extension_attributes or {}
  605. def RightsFromString(xml_string):
  606. return CreateClassFromXMLString(Rights, xml_string)
  607. class Summary(Text):
  608. """The atom:summary element"""
  609. _tag = 'summary'
  610. _namespace = ATOM_NAMESPACE
  611. _children = Text._children.copy()
  612. _attributes = Text._attributes.copy()
  613. def __init__(self, summary_type=None, text=None, extension_elements=None,
  614. extension_attributes=None):
  615. """Constructor for Summary
  616. Args:
  617. summary_type: string
  618. text: str The text data in the this element
  619. extension_elements: list A list of ExtensionElement instances
  620. extension_attributes: dict A dictionary of attribute value string pairs
  621. """
  622. self.type = summary_type
  623. self.text = text
  624. self.extension_elements = extension_elements or []
  625. self.extension_attributes = extension_attributes or {}
  626. def SummaryFromString(xml_string):
  627. return CreateClassFromXMLString(Summary, xml_string)
  628. class Content(Text):
  629. """The atom:content element"""
  630. _tag = 'content'
  631. _namespace = ATOM_NAMESPACE
  632. _children = Text._children.copy()
  633. _attributes = Text._attributes.copy()
  634. _attributes['src'] = 'src'
  635. def __init__(self, content_type=None, src=None, text=None,
  636. extension_elements=None, extension_attributes=None):
  637. """Constructor for Content
  638. Args:
  639. content_type: string
  640. src: string
  641. text: str The text data in the this element
  642. extension_elements: list A list of ExtensionElement instances
  643. extension_attributes: dict A dictionary of attribute value string pairs
  644. """
  645. self.type = content_type
  646. self.src = src
  647. self.text = text
  648. self.extension_elements = extension_elements or []
  649. self.extension_attributes = extension_attributes or {}
  650. def ContentFromString(xml_string):
  651. return CreateClassFromXMLString(Content, xml_string)
  652. class Category(AtomBase):
  653. """The atom:category element"""
  654. _tag = 'category'
  655. _namespace = ATOM_NAMESPACE
  656. _children = AtomBase._children.copy()
  657. _attributes = AtomBase._attributes.copy()
  658. _attributes['term'] = 'term'
  659. _attributes['scheme'] = 'scheme'
  660. _attributes['label'] = 'label'
  661. def __init__(self, term=None, scheme=None, label=None, text=None,
  662. extension_elements=None, extension_attributes=None):
  663. """Constructor for Category
  664. Args:
  665. term: str
  666. scheme: str
  667. label: str
  668. text: str The text data in the this element
  669. extension_elements: list A list of ExtensionElement instances
  670. extension_attributes: dict A dictionary of attribute value string pairs
  671. """
  672. self.term = term
  673. self.scheme = scheme
  674. self.label = label
  675. self.text = text
  676. self.extension_elements = extension_elements or []
  677. self.extension_attributes = extension_attributes or {}
  678. def CategoryFromString(xml_string):
  679. return CreateClassFromXMLString(Category, xml_string)
  680. class Id(AtomBase):
  681. """The atom:id element."""
  682. _tag = 'id'
  683. _namespace = ATOM_NAMESPACE
  684. _children = AtomBase._children.copy()
  685. _attributes = AtomBase._attributes.copy()
  686. def __init__(self, text=None, extension_elements=None,
  687. extension_attributes=None):
  688. """Constructor for Id
  689. Args:
  690. text: str The text data in the this element
  691. extension_elements: list A list of ExtensionElement instances
  692. extension_attributes: dict A dictionary of attribute value string pairs
  693. """
  694. self.text = text
  695. self.extension_elements = extension_elements or []
  696. self.extension_attributes = extension_attributes or {}
  697. def IdFromString(xml_string):
  698. return CreateClassFromXMLString(Id, xml_string)
  699. class Icon(AtomBase):
  700. """The atom:icon element."""
  701. _tag = 'icon'
  702. _namespace = ATOM_NAMESPACE
  703. _children = AtomBase._children.copy()
  704. _attributes = AtomBase._attributes.copy()
  705. def __init__(self, text=None, extension_elements=None,
  706. extension_attributes=None):
  707. """Constructor for Icon
  708. Args:
  709. text: str The text data in the this element
  710. extension_elements: list A list of ExtensionElement instances
  711. extension_attributes: dict A dictionary of attribute value string pairs
  712. """
  713. self.text = text
  714. self.extension_elements = extension_elements or []
  715. self.extension_attributes = extension_attributes or {}
  716. def IconFromString(xml_string):
  717. return CreateClassFromXMLString(Icon, xml_string)
  718. class Logo(AtomBase):
  719. """The atom:logo element."""
  720. _tag = 'logo'
  721. _namespace = ATOM_NAMESPACE
  722. _children = AtomBase._children.copy()
  723. _attributes = AtomBase._attributes.copy()
  724. def __init__(self, text=None, extension_elements=None,
  725. extension_attributes=None):
  726. """Constructor for Logo
  727. Args:
  728. text: str The text data in the this element
  729. extension_elements: list A list of ExtensionElement instances
  730. extension_attributes: dict A dictionary of attribute value string pairs
  731. """
  732. self.text = text
  733. self.extension_elements = extension_elements or []
  734. self.extension_attributes = extension_attributes or {}
  735. def LogoFromString(xml_string):
  736. return CreateClassFromXMLString(Logo, xml_string)
  737. class Draft(AtomBase):
  738. """The app:draft element which indicates if this entry should be public."""
  739. _tag = 'draft'
  740. _namespace = APP_NAMESPACE
  741. _children = AtomBase._children.copy()
  742. _attributes = AtomBase._attributes.copy()
  743. def __init__(self, text=None, extension_elements=None,
  744. extension_attributes=None):
  745. """Constructor for app:draft
  746. Args:
  747. text: str The text data in the this element
  748. extension_elements: list A list of ExtensionElement instances
  749. extension_attributes: dict A dictionary of attribute value string pairs
  750. """
  751. self.text = text
  752. self.extension_elements = extension_elements or []
  753. self.extension_attributes = extension_attributes or {}
  754. def DraftFromString(xml_string):
  755. return CreateClassFromXMLString(Draft, xml_string)
  756. class Control(AtomBase):
  757. """The app:control element indicating restrictions on publication.
  758. The APP control element may contain a draft element indicating whether or
  759. not this entry should be publicly available.
  760. """
  761. _tag = 'control'
  762. _namespace = APP_NAMESPACE
  763. _children = AtomBase._children.copy()
  764. _attributes = AtomBase._attributes.copy()
  765. _children['{%s}draft' % APP_NAMESPACE] = ('draft', Draft)
  766. def __init__(self, draft=None, text=None, extension_elements=None,
  767. extension_attributes=None):
  768. """Constructor for app:control"""
  769. self.draft = draft
  770. self.text = text
  771. self.extension_elements = extension_elements or []
  772. self.extension_attributes = extension_attributes or {}
  773. def ControlFromString(xml_string):
  774. return CreateClassFromXMLString(Control, xml_string)
  775. class Date(AtomBase):
  776. """A parent class for atom:updated, published, etc."""
  777. #TODO Add text to and from time conversion methods to allow users to set
  778. # the contents of a Date to a python DateTime object.
  779. _children = AtomBase._children.copy()
  780. _attributes = AtomBase._attributes.copy()
  781. def __init__(self, text=None, extension_elements=None,
  782. extension_attributes=None):
  783. self.text = text
  784. self.extension_elements = extension_elements or []
  785. self.extension_attributes = extension_attributes or {}
  786. class Updated(Date):
  787. """The atom:updated element."""
  788. _tag = 'updated'
  789. _namespace = ATOM_NAMESPACE
  790. _children = Date._children.copy()
  791. _attributes = Date._attributes.copy()
  792. def __init__(self, text=None, extension_elements=None,
  793. extension_attributes=None):
  794. """Constructor for Updated
  795. Args:
  796. text: str The text data in the this element
  797. extension_elements: list A list of ExtensionElement instances
  798. extension_attributes: dict A dictionary of attribute value string pairs
  799. """
  800. self.text = text
  801. self.extension_elements = extension_elements or []
  802. self.extension_attributes = extension_attributes or {}
  803. def UpdatedFromString(xml_string):
  804. return CreateClassFromXMLString(Updated, xml_string)
  805. class Published(Date):
  806. """The atom:published element."""
  807. _tag = 'published'
  808. _namespace = ATOM_NAMESPACE
  809. _children = Date._children.copy()
  810. _attributes = Date._attributes.copy()
  811. def __init__(self, text=None, extension_elements=None,
  812. extension_attributes=None):
  813. """Constructor for Published
  814. Args:
  815. text: str The text data in the this element
  816. extension_elements: list A list of ExtensionElement instances
  817. extension_attributes: dict A dictionary of attribute value string pairs
  818. """
  819. self.text = text
  820. self.extension_elements = extension_elements or []
  821. self.extension_attributes = extension_attributes or {}
  822. def PublishedFromString(xml_string):
  823. return CreateClassFromXMLString(Published, xml_string)
  824. class LinkFinder(object):
  825. """An "interface" providing methods to find link elements
  826. Entry elements often contain multiple links which differ in the rel
  827. attribute or content type. Often, developers are interested in a specific
  828. type of link so this class provides methods to find specific classes of
  829. links.
  830. This class is used as a mixin in Atom entries and feeds.
  831. """
  832. def GetSelfLink(self):
  833. """Find the first link with rel set to 'self'
  834. Returns:
  835. An atom.Link or none if none of the links had rel equal to 'self'
  836. """
  837. for a_link in self.link:
  838. if a_link.rel == 'self':
  839. return a_link
  840. return None
  841. def GetEditLink(self):
  842. for a_link in self.link:
  843. if a_link.rel == 'edit':
  844. return a_link
  845. return None
  846. def GetEditMediaLink(self):
  847. for a_link in self.link:
  848. if a_link.rel == 'edit-media':
  849. return a_link
  850. return None
  851. def GetNextLink(self):
  852. for a_link in self.link:
  853. if a_link.rel == 'next':
  854. return a_link
  855. return None
  856. def GetLicenseLink(self):
  857. for a_link in self.link:
  858. if a_link.rel == 'license':
  859. return a_link
  860. return None
  861. def GetAlternateLink(self):
  862. for a_link in self.link:
  863. if a_link.rel == 'alternate':
  864. return a_link
  865. return None
  866. class FeedEntryParent(AtomBase, LinkFinder):
  867. """A super class for atom:feed and entry, contains shared attributes"""
  868. _children = AtomBase._children.copy()
  869. _attributes = AtomBase._attributes.copy()
  870. _children['{%s}author' % ATOM_NAMESPACE] = ('author', [Author])
  871. _children['{%s}category' % ATOM_NAMESPACE] = ('category', [Category])
  872. _children['{%s}contributor' % ATOM_NAMESPACE] = ('contributor', [Contributor])
  873. _children['{%s}id' % ATOM_NAMESPACE] = ('id', Id)
  874. _children['{%s}link' % ATOM_NAMESPACE] = ('link', [Link])
  875. _children['{%s}rights' % ATOM_NAMESPACE] = ('rights', Rights)
  876. _children['{%s}title' % ATOM_NAMESPACE] = ('title', Title)
  877. _children['{%s}updated' % ATOM_NAMESPACE] = ('updated', Updated)
  878. def __init__(self, author=None, category=None, contributor=None,
  879. atom_id=None, link=None, rights=None, title=None, updated=None,
  880. text=None, extension_elements=None, extension_attributes=None):
  881. self.author = author or []
  882. self.category = category or []
  883. self.contributor = contributor or []
  884. self.id = atom_id
  885. self.link = link or []
  886. self.rights = rights
  887. self.title = title
  888. self.updated = updated
  889. self.text = text
  890. self.extension_elements = extension_elements or []
  891. self.extension_attributes = extension_attributes or {}
  892. class Source(FeedEntryParent):
  893. """The atom:source element"""
  894. _tag = 'source'
  895. _namespace = ATOM_NAMESPACE
  896. _children = FeedEntryParent._children.copy()
  897. _attributes = FeedEntryParent._attributes.copy()
  898. _children['{%s}generator' % ATOM_NAMESPACE] = ('generator', Generator)
  899. _children['{%s}icon' % ATOM_NAMESPACE] = ('icon', Icon)
  900. _children['{%s}logo' % ATOM_NAMESPACE] = ('logo', Logo)
  901. _children['{%s}subtitle' % ATOM_NAMESPACE] = ('subtitle', Subtitle)
  902. def __init__(self, author=None, category=None, contributor=None,
  903. generator=None, icon=None, atom_id=None, link=None, logo=None,
  904. rights=None, subtitle=None, title=None, updated=None, text=None,
  905. extension_elements=None, extension_attributes=None):
  906. """Constructor for Source
  907. Args:
  908. author: list (optional) A list of Author instances which belong to this
  909. class.
  910. category: list (optional) A list of Category instances
  911. contributor: list (optional) A list on Contributor instances
  912. generator: Generator (optional)
  913. icon: Icon (optional)
  914. id: Id (optional) The entry's Id element
  915. link: list (optional) A list of Link instances
  916. logo: Logo (optional)
  917. rights: Rights (optional) The entry's Rights element
  918. subtitle: Subtitle (optional) The entry's subtitle element
  919. title: Title (optional) the entry's title element
  920. updated: Updated (optional) the entry's updated element
  921. text: String (optional) The text contents of the element. This is the
  922. contents of the Entry's XML text node.
  923. (Example: <foo>This is the text</foo>)
  924. extension_elements: list (optional) A list of ExtensionElement instances
  925. which are children of this element.
  926. extension_attributes: dict (optional) A dictionary of strings which are
  927. the values for additional XML attributes of this element.
  928. """
  929. self.author = author or []
  930. self.category = category or []
  931. self.contributor = contributor or []
  932. self.generator = generator
  933. self.icon = icon
  934. self.id = atom_id
  935. self.link = link or []
  936. self.logo = logo
  937. self.rights = rights
  938. self.subtitle = subtitle
  939. self.title = title
  940. self.updated = updated
  941. self.text = text
  942. self.extension_elements = extension_elements or []
  943. self.extension_attributes = extension_attributes or {}
  944. def SourceFromString(xml_string):
  945. return CreateClassFromXMLString(Source, xml_string)
  946. class Entry(FeedEntryParent):
  947. """The atom:entry element"""
  948. _tag = 'entry'
  949. _namespace = ATOM_NAMESPACE
  950. _children = FeedEntryParent._children.copy()
  951. _attributes = FeedEntryParent._attributes.copy()
  952. _children['{%s}content' % ATOM_NAMESPACE] = ('content', Content)
  953. _children['{%s}published' % ATOM_NAMESPACE] = ('published', Published)
  954. _children['{%s}source' % ATOM_NAMESPACE] = ('source', Source)
  955. _children['{%s}summary' % ATOM_NAMESPACE] = ('summary', Summary)
  956. _children['{%s}control' % APP_NAMESPACE] = ('control', Control)
  957. def __init__(self, author=None, category=None, content=None,
  958. contributor=None, atom_id=None, link=None, published=None, rights=None,
  959. source=None, summary=None, control=None, title=None, updated=None,
  960. extension_elements=None, extension_attributes=None, text=None):
  961. """Constructor for atom:entry
  962. Args:
  963. author: list A list of Author instances which belong to this class.
  964. category: list A list of Category instances
  965. content: Content The entry's Content
  966. contributor: list A list on Contributor instances
  967. id: Id The entry's Id element
  968. link: list A list of Link instances
  969. published: Published The entry's Published element
  970. rights: Rights The entry's Rights element
  971. source: Source the entry's source element
  972. summary: Summary the entry's summary element
  973. title: Title the entry's title element
  974. updated: Updated the entry's updated element
  975. control: The entry's app:control element which can be used to mark an
  976. entry as a draft which should not be publicly viewable.
  977. text: String The text contents of the element. This is the contents
  978. of the Entry's XML text node. (Example: <foo>This is the text</foo>)
  979. extension_elements: list A list of ExtensionElement instances which are
  980. children of this element.
  981. extension_attributes: dict A dictionary of strings which are the values
  982. for additional XML attributes of this element.
  983. """
  984. self.author = author or []
  985. self.category = category or []
  986. self.content = content
  987. self.contributor = contributor or []
  988. self.id = atom_id
  989. self.link = link or []
  990. self.published = published
  991. self.rights = rights
  992. self.source = source
  993. self.summary = summary
  994. self.title = title
  995. self.updated = updated
  996. self.control = control
  997. self.text = text
  998. self.extension_elements = extension_elements or []
  999. self.extension_attributes = extension_attributes or {}
  1000. __init__ = v1_deprecated('Please use atom.data.Entry instead.')(__init__)
  1001. def EntryFromString(xml_string):
  1002. return CreateClassFromXMLString(Entry, xml_string)
  1003. class Feed(Source):
  1004. """The atom:feed element"""
  1005. _tag = 'feed'
  1006. _namespace = ATOM_NAMESPACE
  1007. _children = Source._children.copy()
  1008. _attributes = Source._attributes.copy()
  1009. _children['{%s}entry' % ATOM_NAMESPACE] = ('entry', [Entry])
  1010. def __init__(self, author=None, category=None, contributor=None,
  1011. generator=None, icon=None, atom_id=None, link=None, logo=None,
  1012. rights=None, subtitle=None, title=None, updated=None, entry=None,
  1013. text=None, extension_elements=None, extension_attributes=None):
  1014. """Constructor for Source
  1015. Args:
  1016. author: list (optional) A list of Author instances which belong to this
  1017. class.
  1018. category: list (optional) A list of Category instances
  1019. contributor: list (optional) A list on Contributor instances
  1020. generator: Generator (optional)
  1021. icon: Icon (optional)
  1022. id: Id (optional) The entry's Id element
  1023. link: list (optional) A list of Link instances
  1024. logo: Logo (optional)
  1025. rights: Rights (optional) The entry's Rights element
  1026. subtitle: Subtitle (optional) The entry's subtitle element
  1027. title: Title (optional) the entry's title element
  1028. updated: Updated (optional) the entry's updated element
  1029. entry: list (optional) A list of the Entry instances contained in the
  1030. feed.
  1031. text: String (optional) The text contents of the element. This is the
  1032. contents of the Entry's XML text node.
  1033. (Example: <foo>This is the text</foo>)
  1034. extension_elements: list (optional) A list of ExtensionElement instances
  1035. which are children of this element.
  1036. extension_attributes: dict (optional) A dictionary of strings which are
  1037. the values for additional XML attributes of this element.
  1038. """
  1039. self.author = author or []
  1040. self.category = category or []
  1041. self.contributor = contributor or []
  1042. self.generator = generator
  1043. self.icon = icon
  1044. self.id = atom_id
  1045. self.link = link or []
  1046. self.logo = logo
  1047. self.rights = rights
  1048. self.subtitle = subtitle
  1049. self.title = title
  1050. self.updated = updated
  1051. self.entry = entry or []
  1052. self.text = text
  1053. self.extension_elements = extension_elements or []
  1054. self.extension_attributes = extension_attributes or {}
  1055. __init__ = v1_deprecated('Please use atom.data.Feed instead.')(__init__)
  1056. def FeedFromString(xml_string):
  1057. return CreateClassFromXMLString(Feed, xml_string)
  1058. class ExtensionElement(object):
  1059. """Represents extra XML elements contained in Atom classes."""
  1060. def __init__(self, tag, namespace=None, attributes=None,
  1061. children=None, text=None):
  1062. """Constructor for EtensionElement
  1063. Args:
  1064. namespace: string (optional) The XML namespace for this element.
  1065. tag: string (optional) The tag (without the namespace qualifier) for
  1066. this element. To reconstruct the full qualified name of the element,
  1067. combine this tag with the namespace.
  1068. attributes: dict (optinal) The attribute value string pairs for the XML
  1069. attributes of this element.
  1070. children: list (optional) A list of ExtensionElements which represent
  1071. the XML child nodes of this element.
  1072. """
  1073. self.namespace = namespace
  1074. self.tag = tag
  1075. self.attributes = attributes or {}
  1076. self.children = children or []
  1077. self.text = text
  1078. def ToString(self):
  1079. element_tree = self._TransferToElementTree(ElementTree.Element(''))
  1080. return ElementTree.tostring(element_tree, encoding="UTF-8")
  1081. def _TransferToElementTree(self, element_tree):
  1082. if self.tag is None:
  1083. return None
  1084. if self.namespace is not None:
  1085. element_tree.tag = '{%s}%s' % (self.namespace, self.tag)
  1086. else:
  1087. element_tree.tag = self.tag
  1088. for key, value in self.attributes.iteritems():
  1089. element_tree.attrib[key] = value
  1090. for child in self.children:
  1091. child._BecomeChildElement(element_tree)
  1092. element_tree.text = self.text
  1093. return element_tree
  1094. def _BecomeChildElement(self, element_tree):
  1095. """Converts this object into an etree element and adds it as a child node.
  1096. Adds self to the ElementTree. This method is required to avoid verbose XML
  1097. which constantly redefines the namespace.
  1098. Args:
  1099. element_tree: ElementTree._Element The element to which this object's XML
  1100. will be added.
  1101. """
  1102. new_element = ElementTree.Element('')
  1103. element_tree.append(new_element)
  1104. self._TransferToElementTree(new_element)
  1105. def FindChildren(self, tag=None, namespace=None):
  1106. """Searches child nodes for objects with the desired tag/namespace.
  1107. Returns a list of extension elements within this object whose tag
  1108. and/or namespace match those passed in. To find all children in
  1109. a particular namespace, specify the namespace but not the tag name.
  1110. If you specify only the tag, the result list may contain extension
  1111. elements in multiple namespaces.
  1112. Args:
  1113. tag: str (optional) The desired tag
  1114. namespace: str (optional) The desired namespace
  1115. Returns:
  1116. A list of elements whose tag and/or namespace match the parameters
  1117. values
  1118. """
  1119. results = []
  1120. if tag and namespace:
  1121. for element in self.children:
  1122. if element.tag == tag and element.namespace == namespace:
  1123. results.append(element)
  1124. elif tag and not namespace:
  1125. for element in self.children:
  1126. if element.tag == tag:
  1127. results.append(element)
  1128. elif namespace and not tag:
  1129. for element in self.children:
  1130. if element.namespace == namespace:
  1131. results.append(element)
  1132. else:
  1133. for element in self.children:
  1134. results.append(element)
  1135. return results
  1136. def ExtensionElementFromString(xml_string):
  1137. element_tree = ElementTree.fromstring(xml_string)
  1138. return _ExtensionElementFromElementTree(element_tree)
  1139. def _ExtensionElementFromElementTree(element_tree):
  1140. element_tag = element_tree.tag
  1141. if '}' in element_tag:
  1142. namespace = element_tag[1:element_tag.index('}')]
  1143. tag = element_tag[element_tag.index('}')+1:]
  1144. else:
  1145. namespace = None
  1146. tag = element_tag
  1147. extension = ExtensionElement(namespace=namespace, tag=tag)
  1148. for key, value in element_tree.attrib.iteritems():
  1149. extension.attributes[key] = value
  1150. for child in element_tree:
  1151. extension.children.append(_ExtensionElementFromElementTree(child))
  1152. extension.text = element_tree.text
  1153. return extension
  1154. def deprecated(warning=None):
  1155. """Decorator to raise warning each time the function is called.
  1156. Args:
  1157. warning: The warning message to be displayed as a string (optinoal).
  1158. """
  1159. warning = warning or ''
  1160. # This closure is what is returned from the deprecated function.
  1161. def mark_deprecated(f):
  1162. # The deprecated_function wraps the actual call to f.
  1163. def deprecated_function(*args, **kwargs):
  1164. warnings.warn(warning, DeprecationWarning, stacklevel=2)
  1165. return f(*args, **kwargs)
  1166. # Preserve the original name to avoid masking all decorated functions as
  1167. # 'deprecated_function'
  1168. try:
  1169. deprecated_function.func_name = f.func_name
  1170. except TypeError:
  1171. # Setting the func_name is not allowed in Python2.3.
  1172. pass
  1173. return deprecated_function
  1174. return mark_deprecated