diff --git a/pyproject.toml b/pyproject.toml index 9e9b19d9..1a33f018 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,8 +108,5 @@ fixable = [] # Don't auto-fix — we want to review changes manually "src/gam/util/**" = ["E402", "F821"] # gamlib modules "src/gam/gamlib/**" = ["F821"] -# gdata/ and atom/ are vendored third-party code — don't lint them -"src/gam/gdata/**" = ["F", "E", "UP"] -"src/gam/atom/**" = ["F", "E", "UP"] # Test files can be more relaxed "tests/**" = ["F401", "F811"] diff --git a/src/gam/atom/__init__.py b/src/gam/atom/__init__.py deleted file mode 100644 index c4a382c4..00000000 --- a/src/gam/atom/__init__.py +++ /dev/null @@ -1,1460 +0,0 @@ -# -# Copyright (C) 2006 Google Inc. -# -# Licensed under the Apache License 2.0; - - - -"""Contains classes representing Atom elements. - - Module objective: provide data classes for Atom constructs. These classes hide - the XML-ness of Atom and provide a set of native Python classes to interact - with. - - Conversions to and from XML should only be necessary when the Atom classes - "touch the wire" and are sent over HTTP. For this reason this module - provides methods and functions to convert Atom classes to and from strings. - - For more information on the Atom data model, see RFC 4287 - (http://www.ietf.org/rfc/rfc4287.txt) - - AtomBase: A foundation class on which Atom classes are built. It - handles the parsing of attributes and children which are common to all - Atom classes. By default, the AtomBase class translates all XML child - nodes into ExtensionElements. - - ExtensionElement: Atom allows Atom objects to contain XML which is not part - of the Atom specification, these are called extension elements. If a - classes parser encounters an unexpected XML construct, it is translated - into an ExtensionElement instance. ExtensionElement is designed to fully - capture the information in the XML. Child nodes in an XML extension are - turned into ExtensionElements as well. -""" -from functools import wraps - -# __author__ = 'api.jscudder (Jeffrey Scudder)' - -import lxml.etree as ElementTree -import warnings - -# XML namespaces which are often used in Atom entities. -ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom' -ELEMENT_TEMPLATE = '{http://www.w3.org/2005/Atom}%s' -APP_NAMESPACE = 'http://purl.org/atom/app#' -APP_TEMPLATE = '{http://purl.org/atom/app#}%s' - -# This encoding is used for converting strings before translating the XML -# into an object. -XML_STRING_ENCODING = 'utf-8' -# The desired string encoding for object members. set or monkey-patch to -# unicode if you want object members to be Python unicode strings, instead of -# encoded strings -MEMBER_STRING_ENCODING = str -#MEMBER_STRING_ENCODING = 'utf-8' -# MEMBER_STRING_ENCODING = unicode - -# If True, all methods which are exclusive to v1 will raise a -# DeprecationWarning -ENABLE_V1_WARNINGS = False - - -def v1_deprecated(warning=None): - """Shows a warning if ENABLE_V1_WARNINGS is True. - - Function decorator used to mark methods used in v1 classes which - may be removed in future versions of the library. - """ - warning = warning or '' - - # This closure is what is returned from the deprecated function. - def mark_deprecated(f): - # The deprecated_function wraps the actual call to f. - @wraps(f) - def optional_warn_function(*args, **kwargs): - if ENABLE_V1_WARNINGS: - warnings.warn(warning, DeprecationWarning, stacklevel=2) - return f(*args, **kwargs) - - return optional_warn_function - - return mark_deprecated - - -def CreateClassFromXMLString(target_class, xml_string, string_encoding=None): - """Creates an instance of the target class from the string contents. - - Args: - target_class: class The class which will be instantiated and populated - with the contents of the XML. This class must have a _tag and a - _namespace class variable. - xml_string: str A string which contains valid XML. The root element - of the XML string should match the tag and namespace of the desired - class. - string_encoding: str The character encoding which the xml_string should - be converted to before it is interpreted and translated into - objects. The default is None in which case the string encoding - is not changed. - - Returns: - An instance of the target class with members assigned according to the - contents of the XML - or None if the root XML tag and namespace did not - match those of the target class. - """ - encoding = string_encoding or XML_STRING_ENCODING - if encoding and isinstance(xml_string, str): - xml_string = xml_string.encode(encoding) - tree = ElementTree.fromstring(xml_string) - return _CreateClassFromElementTree(target_class, tree) - - -CreateClassFromXMLString = v1_deprecated( - 'Please use atom.core.parse with atom.data classes instead.')( - CreateClassFromXMLString) - - -def _CreateClassFromElementTree(target_class, tree, namespace=None, tag=None): - """Instantiates the class and populates members according to the tree. - - Note: Only use this function with classes that have _namespace and _tag - class members. - - Args: - target_class: class The class which will be instantiated and populated - with the contents of the XML. - tree: ElementTree An element tree whose contents will be converted into - members of the new target_class instance. - namespace: str (optional) The namespace which the XML tree's root node must - match. If omitted, the namespace defaults to the _namespace of the - target class. - tag: str (optional) The tag which the XML tree's root node must match. If - omitted, the tag defaults to the _tag class member of the target - class. - - Returns: - An instance of the target class - or None if the tag and namespace of - the XML tree's root node did not match the desired namespace and tag. - """ - if namespace is None: - namespace = target_class._namespace - if tag is None: - tag = target_class._tag - if tree.tag == '{%s}%s' % (namespace, tag): - target = target_class() - target._HarvestElementTree(tree) - return target - else: - return None - - -class ExtensionContainer(object): - def __init__(self, extension_elements=None, extension_attributes=None, - text=None): - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - self.text = text - - __init__ = v1_deprecated( - 'Please use data model classes in atom.data instead.')( - __init__) - - # Three methods to create an object from an ElementTree - def _HarvestElementTree(self, tree): - # Fill in the instance members from the contents of the XML tree. - for child in tree: - self._ConvertElementTreeToMember(child) - for attribute, value in tree.attrib.items(): - self._ConvertElementAttributeToMember(attribute, value) - # Encode the text string according to the desired encoding type. (UTF-8) - if tree.text: - if MEMBER_STRING_ENCODING is str: - self.text = tree.text - else: - self.text = tree.text.encode(MEMBER_STRING_ENCODING) - - def _ConvertElementTreeToMember(self, child_tree, current_class=None): - self.extension_elements.append(_ExtensionElementFromElementTree( - child_tree)) - - def _ConvertElementAttributeToMember(self, attribute, value): - # Encode the attribute value's string with the desired type Default UTF-8 - if value: - if MEMBER_STRING_ENCODING is str: - self.extension_attributes[attribute] = value - else: - self.extension_attributes[attribute] = value.encode( - MEMBER_STRING_ENCODING) - - # One method to create an ElementTree from an object - def _AddMembersToElementTree(self, tree): - for child in self.extension_elements: - child._BecomeChildElement(tree) - for attribute, value in self.extension_attributes.items(): - if value: - if isinstance(value, str) or MEMBER_STRING_ENCODING is str: - tree.attrib[attribute] = value - else: - # Decode the value from the desired encoding (default UTF-8). - tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING) - if self.text: - if isinstance(self.text, str) or MEMBER_STRING_ENCODING is str: - tree.text = self.text - else: - tree.text = self.text.decode(MEMBER_STRING_ENCODING) - - def FindExtensions(self, tag=None, namespace=None): - """Searches extension elements for child nodes with the desired name. - - Returns a list of extension elements within this object whose tag - and/or namespace match those passed in. To find all extensions in - a particular namespace, specify the namespace but not the tag name. - If you specify only the tag, the result list may contain extension - elements in multiple namespaces. - - Args: - tag: str (optional) The desired tag - namespace: str (optional) The desired namespace - - Returns: - A list of elements whose tag and/or namespace match the parameters - values - """ - - results = [] - - if tag and namespace: - for element in self.extension_elements: - if element.tag == tag and element.namespace == namespace: - results.append(element) - elif tag and not namespace: - for element in self.extension_elements: - if element.tag == tag: - results.append(element) - elif namespace and not tag: - for element in self.extension_elements: - if element.namespace == namespace: - results.append(element) - else: - for element in self.extension_elements: - results.append(element) - - return results - - -class AtomBase(ExtensionContainer): - _children = {} - _attributes = {} - - def __init__(self, extension_elements=None, extension_attributes=None, - text=None): - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - self.text = text - - __init__ = v1_deprecated( - 'Please use data model classes in atom.data instead.')( - __init__) - - def _ConvertElementTreeToMember(self, child_tree): - # Find the element's tag in this class's list of child members - if child_tree.tag in self.__class__._children: - member_name = self.__class__._children[child_tree.tag][0] - member_class = self.__class__._children[child_tree.tag][1] - # If the class member is supposed to contain a list, make sure the - # matching member is set to a list, then append the new member - # instance to the list. - if isinstance(member_class, list): - if getattr(self, member_name) is None: - setattr(self, member_name, []) - getattr(self, member_name).append(_CreateClassFromElementTree( - member_class[0], child_tree)) - else: - setattr(self, member_name, - _CreateClassFromElementTree(member_class, child_tree)) - else: - ExtensionContainer._ConvertElementTreeToMember(self, child_tree) - - def _ConvertElementAttributeToMember(self, attribute, value): - # Find the attribute in this class's list of attributes. - if attribute in self.__class__._attributes: - # Find the member of this class which corresponds to the XML attribute - # (lookup in current_class._attributes) and set this member to the - # desired value (using self.__dict__). - if value: - # Encode the string to capture non-ascii characters (default UTF-8) - if MEMBER_STRING_ENCODING is str: - setattr(self, self.__class__._attributes[attribute], value) - else: - setattr(self, self.__class__._attributes[attribute], - value.encode(MEMBER_STRING_ENCODING)) - else: - ExtensionContainer._ConvertElementAttributeToMember( - self, attribute, value) - - # Three methods to create an ElementTree from an object - def _AddMembersToElementTree(self, tree): - # Convert the members of this class which are XML child nodes. - # This uses the class's _children dictionary to find the members which - # should become XML child nodes. - member_node_names = [values[0] for tag, values in - self.__class__._children.items()] - for member_name in member_node_names: - member = getattr(self, member_name) - if member is None: - pass - elif isinstance(member, list): - for instance in member: - instance._BecomeChildElement(tree) - else: - member._BecomeChildElement(tree) - # Convert the members of this class which are XML attributes. - for xml_attribute, member_name in self.__class__._attributes.items(): - member = getattr(self, member_name) - if member is not None: - if isinstance(member, str) or MEMBER_STRING_ENCODING is str: - tree.attrib[xml_attribute] = member - else: - tree.attrib[xml_attribute] = member.decode(MEMBER_STRING_ENCODING) - # Lastly, call the ExtensionContainers's _AddMembersToElementTree to - # convert any extension attributes. - ExtensionContainer._AddMembersToElementTree(self, tree) - - def _BecomeChildElement(self, tree): - """ - - Note: Only for use with classes that have a _tag and _namespace class - member. It is in AtomBase so that it can be inherited but it should - not be called on instances of AtomBase. - - """ - new_child = ElementTree.Element('tag__') - tree.append(new_child) - new_child.tag = '{%s}%s' % (self.__class__._namespace, - self.__class__._tag) - self._AddMembersToElementTree(new_child) - - def _ToElementTree(self): - """ - - Note, this method is designed to be used only with classes that have a - _tag and _namespace. It is placed in AtomBase for inheritance but should - not be called on this class. - - """ - new_tree = ElementTree.Element('{%s}%s' % (self.__class__._namespace, - self.__class__._tag)) - self._AddMembersToElementTree(new_tree) - return new_tree - - def ToString(self, string_encoding=str): - """Converts the Atom object to a string containing XML.""" - return ElementTree.tostring(self._ToElementTree(), encoding=string_encoding) - - def __str__(self): - return self.ToString() - - -class Name(AtomBase): - """The atom:name element""" - - _tag = 'name' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Name - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def NameFromString(xml_string): - return CreateClassFromXMLString(Name, xml_string) - - -class Email(AtomBase): - """The atom:email element""" - - _tag = 'email' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Email - - Args: - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - text: str The text data in the this element - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def EmailFromString(xml_string): - return CreateClassFromXMLString(Email, xml_string) - - -class Uri(AtomBase): - """The atom:uri element""" - - _tag = 'uri' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Uri - - Args: - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - text: str The text data in the this element - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def UriFromString(xml_string): - return CreateClassFromXMLString(Uri, xml_string) - - -class Person(AtomBase): - """A foundation class from which atom:author and atom:contributor extend. - - A person contains information like name, email address, and web page URI for - an author or contributor to an Atom feed. - """ - - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _children['{%s}name' % (ATOM_NAMESPACE)] = ('name', Name) - _children['{%s}email' % (ATOM_NAMESPACE)] = ('email', Email) - _children['{%s}uri' % (ATOM_NAMESPACE)] = ('uri', Uri) - - def __init__(self, name=None, email=None, uri=None, - extension_elements=None, extension_attributes=None, text=None): - """Foundation from which author and contributor are derived. - - The constructor is provided for illustrative purposes, you should not - need to instantiate a Person. - - Args: - name: Name The person's name - email: Email The person's email address - uri: Uri The URI of the person's webpage - extension_elements: list A list of ExtensionElement instances which are - children of this element. - extension_attributes: dict A dictionary of strings which are the values - for additional XML attributes of this element. - text: String The text contents of the element. This is the contents - of the Entry's XML text node. (Example: This is the text) - """ - - self.name = name - self.email = email - self.uri = uri - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - self.text = text - - -class Author(Person): - """The atom:author element - - An author is a required element in Feed. - """ - - _tag = 'author' - _namespace = ATOM_NAMESPACE - _children = Person._children.copy() - _attributes = Person._attributes.copy() - - # _children = {} - # _attributes = {} - - def __init__(self, name=None, email=None, uri=None, - extension_elements=None, extension_attributes=None, text=None): - """Constructor for Author - - Args: - name: Name - email: Email - uri: Uri - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - text: str The text data in the this element - """ - - self.name = name - self.email = email - self.uri = uri - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - self.text = text - - -def AuthorFromString(xml_string): - return CreateClassFromXMLString(Author, xml_string) - - -class Contributor(Person): - """The atom:contributor element""" - - _tag = 'contributor' - _namespace = ATOM_NAMESPACE - _children = Person._children.copy() - _attributes = Person._attributes.copy() - - def __init__(self, name=None, email=None, uri=None, - extension_elements=None, extension_attributes=None, text=None): - """Constructor for Contributor - - Args: - name: Name - email: Email - uri: Uri - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - text: str The text data in the this element - """ - - self.name = name - self.email = email - self.uri = uri - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - self.text = text - - -def ContributorFromString(xml_string): - return CreateClassFromXMLString(Contributor, xml_string) - - -class Link(AtomBase): - """The atom:link element""" - - _tag = 'link' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _attributes['rel'] = 'rel' - _attributes['href'] = 'href' - _attributes['type'] = 'type' - _attributes['title'] = 'title' - _attributes['length'] = 'length' - _attributes['hreflang'] = 'hreflang' - - def __init__(self, href=None, rel=None, link_type=None, hreflang=None, - title=None, length=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Link - - Args: - href: string The href attribute of the link - rel: string - type: string - hreflang: string The language for the href - title: string - length: string The length of the href's destination - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - text: str The text data in the this element - """ - - self.href = href - self.rel = rel - self.type = link_type - self.hreflang = hreflang - self.title = title - self.length = length - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def LinkFromString(xml_string): - return CreateClassFromXMLString(Link, xml_string) - - -class Generator(AtomBase): - """The atom:generator element""" - - _tag = 'generator' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _attributes['uri'] = 'uri' - _attributes['version'] = 'version' - - def __init__(self, uri=None, version=None, text=None, - extension_elements=None, extension_attributes=None): - """Constructor for Generator - - Args: - uri: string - version: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.uri = uri - self.version = version - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def GeneratorFromString(xml_string): - return CreateClassFromXMLString(Generator, xml_string) - - -class Text(AtomBase): - """A foundation class from which atom:title, summary, etc. extend. - - This class should never be instantiated. - """ - - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _attributes['type'] = 'type' - - def __init__(self, text_type=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Text - - Args: - text_type: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.type = text_type - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -class Title(Text): - """The atom:title element""" - - _tag = 'title' - _namespace = ATOM_NAMESPACE - _children = Text._children.copy() - _attributes = Text._attributes.copy() - - def __init__(self, title_type=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Title - - Args: - title_type: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.type = title_type - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def TitleFromString(xml_string): - return CreateClassFromXMLString(Title, xml_string) - - -class Subtitle(Text): - """The atom:subtitle element""" - - _tag = 'subtitle' - _namespace = ATOM_NAMESPACE - _children = Text._children.copy() - _attributes = Text._attributes.copy() - - def __init__(self, subtitle_type=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Subtitle - - Args: - subtitle_type: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.type = subtitle_type - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def SubtitleFromString(xml_string): - return CreateClassFromXMLString(Subtitle, xml_string) - - -class Rights(Text): - """The atom:rights element""" - - _tag = 'rights' - _namespace = ATOM_NAMESPACE - _children = Text._children.copy() - _attributes = Text._attributes.copy() - - def __init__(self, rights_type=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Rights - - Args: - rights_type: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.type = rights_type - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def RightsFromString(xml_string): - return CreateClassFromXMLString(Rights, xml_string) - - -class Summary(Text): - """The atom:summary element""" - - _tag = 'summary' - _namespace = ATOM_NAMESPACE - _children = Text._children.copy() - _attributes = Text._attributes.copy() - - def __init__(self, summary_type=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Summary - - Args: - summary_type: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.type = summary_type - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def SummaryFromString(xml_string): - return CreateClassFromXMLString(Summary, xml_string) - - -class Content(Text): - """The atom:content element""" - - _tag = 'content' - _namespace = ATOM_NAMESPACE - _children = Text._children.copy() - _attributes = Text._attributes.copy() - _attributes['src'] = 'src' - - def __init__(self, content_type=None, src=None, text=None, - extension_elements=None, extension_attributes=None): - """Constructor for Content - - Args: - content_type: string - src: string - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.type = content_type - self.src = src - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def ContentFromString(xml_string): - return CreateClassFromXMLString(Content, xml_string) - - -class Category(AtomBase): - """The atom:category element""" - - _tag = 'category' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _attributes['term'] = 'term' - _attributes['scheme'] = 'scheme' - _attributes['label'] = 'label' - - def __init__(self, term=None, scheme=None, label=None, text=None, - extension_elements=None, extension_attributes=None): - """Constructor for Category - - Args: - term: str - scheme: str - label: str - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.term = term - self.scheme = scheme - self.label = label - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def CategoryFromString(xml_string): - return CreateClassFromXMLString(Category, xml_string) - - -class Id(AtomBase): - """The atom:id element.""" - - _tag = 'id' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Id - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def IdFromString(xml_string): - return CreateClassFromXMLString(Id, xml_string) - - -class Icon(AtomBase): - """The atom:icon element.""" - - _tag = 'icon' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Icon - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def IconFromString(xml_string): - return CreateClassFromXMLString(Icon, xml_string) - - -class Logo(AtomBase): - """The atom:logo element.""" - - _tag = 'logo' - _namespace = ATOM_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Logo - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def LogoFromString(xml_string): - return CreateClassFromXMLString(Logo, xml_string) - - -class Draft(AtomBase): - """The app:draft element which indicates if this entry should be public.""" - - _tag = 'draft' - _namespace = APP_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for app:draft - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def DraftFromString(xml_string): - return CreateClassFromXMLString(Draft, xml_string) - - -class Control(AtomBase): - """The app:control element indicating restrictions on publication. - - The APP control element may contain a draft element indicating whether or - not this entry should be publicly available. - """ - - _tag = 'control' - _namespace = APP_NAMESPACE - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _children['{%s}draft' % APP_NAMESPACE] = ('draft', Draft) - - def __init__(self, draft=None, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for app:control""" - - self.draft = draft - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def ControlFromString(xml_string): - return CreateClassFromXMLString(Control, xml_string) - - -class Date(AtomBase): - """A parent class for atom:updated, published, etc.""" - - # TODO Add text to and from time conversion methods to allow users to set - # the contents of a Date to a python DateTime object. - - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -class Updated(Date): - """The atom:updated element.""" - - _tag = 'updated' - _namespace = ATOM_NAMESPACE - _children = Date._children.copy() - _attributes = Date._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Updated - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def UpdatedFromString(xml_string): - return CreateClassFromXMLString(Updated, xml_string) - - -class Published(Date): - """The atom:published element.""" - - _tag = 'published' - _namespace = ATOM_NAMESPACE - _children = Date._children.copy() - _attributes = Date._attributes.copy() - - def __init__(self, text=None, extension_elements=None, - extension_attributes=None): - """Constructor for Published - - Args: - text: str The text data in the this element - extension_elements: list A list of ExtensionElement instances - extension_attributes: dict A dictionary of attribute value string pairs - """ - - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def PublishedFromString(xml_string): - return CreateClassFromXMLString(Published, xml_string) - - -class LinkFinder(object): - """An "interface" providing methods to find link elements - - Entry elements often contain multiple links which differ in the rel - attribute or content type. Often, developers are interested in a specific - type of link so this class provides methods to find specific classes of - links. - - This class is used as a mixin in Atom entries and feeds. - """ - - def GetSelfLink(self): - """Find the first link with rel set to 'self' - - Returns: - An atom.Link or none if none of the links had rel equal to 'self' - """ - - for a_link in self.link: - if a_link.rel == 'self': - return a_link - return None - - def GetEditLink(self): - for a_link in self.link: - if a_link.rel == 'edit': - return a_link - return None - - def GetEditMediaLink(self): - for a_link in self.link: - if a_link.rel == 'edit-media': - return a_link - return None - - def GetNextLink(self): - for a_link in self.link: - if a_link.rel == 'next': - return a_link - return None - - def GetLicenseLink(self): - for a_link in self.link: - if a_link.rel == 'license': - return a_link - return None - - def GetAlternateLink(self): - for a_link in self.link: - if a_link.rel == 'alternate': - return a_link - return None - - -class FeedEntryParent(AtomBase, LinkFinder): - """A super class for atom:feed and entry, contains shared attributes""" - - _children = AtomBase._children.copy() - _attributes = AtomBase._attributes.copy() - _children['{%s}author' % ATOM_NAMESPACE] = ('author', [Author]) - _children['{%s}category' % ATOM_NAMESPACE] = ('category', [Category]) - _children['{%s}contributor' % ATOM_NAMESPACE] = ('contributor', [Contributor]) - _children['{%s}id' % ATOM_NAMESPACE] = ('id', Id) - _children['{%s}link' % ATOM_NAMESPACE] = ('link', [Link]) - _children['{%s}rights' % ATOM_NAMESPACE] = ('rights', Rights) - _children['{%s}title' % ATOM_NAMESPACE] = ('title', Title) - _children['{%s}updated' % ATOM_NAMESPACE] = ('updated', Updated) - - def __init__(self, author=None, category=None, contributor=None, - atom_id=None, link=None, rights=None, title=None, updated=None, - text=None, extension_elements=None, extension_attributes=None): - self.author = author or [] - self.category = category or [] - self.contributor = contributor or [] - self.id = atom_id - self.link = link or [] - self.rights = rights - self.title = title - self.updated = updated - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -class Source(FeedEntryParent): - """The atom:source element""" - - _tag = 'source' - _namespace = ATOM_NAMESPACE - _children = FeedEntryParent._children.copy() - _attributes = FeedEntryParent._attributes.copy() - _children['{%s}generator' % ATOM_NAMESPACE] = ('generator', Generator) - _children['{%s}icon' % ATOM_NAMESPACE] = ('icon', Icon) - _children['{%s}logo' % ATOM_NAMESPACE] = ('logo', Logo) - _children['{%s}subtitle' % ATOM_NAMESPACE] = ('subtitle', Subtitle) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, text=None, - extension_elements=None, extension_attributes=None): - """Constructor for Source - - Args: - author: list (optional) A list of Author instances which belong to this - class. - category: list (optional) A list of Category instances - contributor: list (optional) A list on Contributor instances - generator: Generator (optional) - icon: Icon (optional) - id: Id (optional) The entry's Id element - link: list (optional) A list of Link instances - logo: Logo (optional) - rights: Rights (optional) The entry's Rights element - subtitle: Subtitle (optional) The entry's subtitle element - title: Title (optional) the entry's title element - updated: Updated (optional) the entry's updated element - text: String (optional) The text contents of the element. This is the - contents of the Entry's XML text node. - (Example: This is the text) - extension_elements: list (optional) A list of ExtensionElement instances - which are children of this element. - extension_attributes: dict (optional) A dictionary of strings which are - the values for additional XML attributes of this element. - """ - - self.author = author or [] - self.category = category or [] - self.contributor = contributor or [] - self.generator = generator - self.icon = icon - self.id = atom_id - self.link = link or [] - self.logo = logo - self.rights = rights - self.subtitle = subtitle - self.title = title - self.updated = updated - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def SourceFromString(xml_string): - return CreateClassFromXMLString(Source, xml_string) - - -class Entry(FeedEntryParent): - """The atom:entry element""" - - _tag = 'entry' - _namespace = ATOM_NAMESPACE - _children = FeedEntryParent._children.copy() - _attributes = FeedEntryParent._attributes.copy() - _children['{%s}content' % ATOM_NAMESPACE] = ('content', Content) - _children['{%s}published' % ATOM_NAMESPACE] = ('published', Published) - _children['{%s}source' % ATOM_NAMESPACE] = ('source', Source) - _children['{%s}summary' % ATOM_NAMESPACE] = ('summary', Summary) - _children['{%s}control' % APP_NAMESPACE] = ('control', Control) - - def __init__(self, author=None, category=None, content=None, - contributor=None, atom_id=None, link=None, published=None, rights=None, - source=None, summary=None, control=None, title=None, updated=None, - extension_elements=None, extension_attributes=None, text=None): - """Constructor for atom:entry - - Args: - author: list A list of Author instances which belong to this class. - category: list A list of Category instances - content: Content The entry's Content - contributor: list A list on Contributor instances - id: Id The entry's Id element - link: list A list of Link instances - published: Published The entry's Published element - rights: Rights The entry's Rights element - source: Source the entry's source element - summary: Summary the entry's summary element - title: Title the entry's title element - updated: Updated the entry's updated element - control: The entry's app:control element which can be used to mark an - entry as a draft which should not be publicly viewable. - text: String The text contents of the element. This is the contents - of the Entry's XML text node. (Example: This is the text) - extension_elements: list A list of ExtensionElement instances which are - children of this element. - extension_attributes: dict A dictionary of strings which are the values - for additional XML attributes of this element. - """ - - self.author = author or [] - self.category = category or [] - self.content = content - self.contributor = contributor or [] - self.id = atom_id - self.link = link or [] - self.published = published - self.rights = rights - self.source = source - self.summary = summary - self.title = title - self.updated = updated - self.control = control - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - __init__ = v1_deprecated('Please use atom.data.Entry instead.')(__init__) - - -def EntryFromString(xml_string): - return CreateClassFromXMLString(Entry, xml_string) - - -class Feed(Source): - """The atom:feed element""" - - _tag = 'feed' - _namespace = ATOM_NAMESPACE - _children = Source._children.copy() - _attributes = Source._attributes.copy() - _children['{%s}entry' % ATOM_NAMESPACE] = ('entry', [Entry]) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, entry=None, - text=None, extension_elements=None, extension_attributes=None): - """Constructor for Source - - Args: - author: list (optional) A list of Author instances which belong to this - class. - category: list (optional) A list of Category instances - contributor: list (optional) A list on Contributor instances - generator: Generator (optional) - icon: Icon (optional) - id: Id (optional) The entry's Id element - link: list (optional) A list of Link instances - logo: Logo (optional) - rights: Rights (optional) The entry's Rights element - subtitle: Subtitle (optional) The entry's subtitle element - title: Title (optional) the entry's title element - updated: Updated (optional) the entry's updated element - entry: list (optional) A list of the Entry instances contained in the - feed. - text: String (optional) The text contents of the element. This is the - contents of the Entry's XML text node. - (Example: This is the text) - extension_elements: list (optional) A list of ExtensionElement instances - which are children of this element. - extension_attributes: dict (optional) A dictionary of strings which are - the values for additional XML attributes of this element. - """ - - self.author = author or [] - self.category = category or [] - self.contributor = contributor or [] - self.generator = generator - self.icon = icon - self.id = atom_id - self.link = link or [] - self.logo = logo - self.rights = rights - self.subtitle = subtitle - self.title = title - self.updated = updated - self.entry = entry or [] - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - __init__ = v1_deprecated('Please use atom.data.Feed instead.')(__init__) - - -def FeedFromString(xml_string): - return CreateClassFromXMLString(Feed, xml_string) - - -class ExtensionElement(object): - """Represents extra XML elements contained in Atom classes.""" - - def __init__(self, tag, namespace=None, attributes=None, - children=None, text=None): - """Constructor for EtensionElement - - Args: - namespace: string (optional) The XML namespace for this element. - tag: string (optional) The tag (without the namespace qualifier) for - this element. To reconstruct the full qualified name of the element, - combine this tag with the namespace. - attributes: dict (optinal) The attribute value string pairs for the XML - attributes of this element. - children: list (optional) A list of ExtensionElements which represent - the XML child nodes of this element. - """ - - self.namespace = namespace - self.tag = tag - self.attributes = attributes or {} - self.children = children or [] - self.text = text - - def ToString(self): - element_tree = self._TransferToElementTree(ElementTree.Element('tag__')) - return ElementTree.tostring(element_tree, encoding="UTF-8") - - def _TransferToElementTree(self, element_tree): - if self.tag is None: - return None - - if self.namespace is not None: - element_tree.tag = '{%s}%s' % (self.namespace, self.tag) - else: - element_tree.tag = self.tag - - for key, value in self.attributes.items(): - element_tree.attrib[key] = value - - for child in self.children: - child._BecomeChildElement(element_tree) - - element_tree.text = self.text - - return element_tree - - def _BecomeChildElement(self, element_tree): - """Converts this object into an etree element and adds it as a child node. - - Adds self to the ElementTree. This method is required to avoid verbose XML - which constantly redefines the namespace. - - Args: - element_tree: ElementTree._Element The element to which this object's XML - will be added. - """ - new_element = ElementTree.Element('tag__') # uh, uhm... empty tag name - sorry google, this is bogus? (c)https://github.com/lqc - element_tree.append(new_element) - self._TransferToElementTree(new_element) - - def FindChildren(self, tag=None, namespace=None): - """Searches child nodes for objects with the desired tag/namespace. - - Returns a list of extension elements within this object whose tag - and/or namespace match those passed in. To find all children in - a particular namespace, specify the namespace but not the tag name. - If you specify only the tag, the result list may contain extension - elements in multiple namespaces. - - Args: - tag: str (optional) The desired tag - namespace: str (optional) The desired namespace - - Returns: - A list of elements whose tag and/or namespace match the parameters - values - """ - - results = [] - - if tag and namespace: - for element in self.children: - if element.tag == tag and element.namespace == namespace: - results.append(element) - elif tag and not namespace: - for element in self.children: - if element.tag == tag: - results.append(element) - elif namespace and not tag: - for element in self.children: - if element.namespace == namespace: - results.append(element) - else: - for element in self.children: - results.append(element) - - return results - - -def ExtensionElementFromString(xml_string): - element_tree = ElementTree.fromstring(xml_string) - return _ExtensionElementFromElementTree(element_tree) - - -def _ExtensionElementFromElementTree(element_tree): - element_tag = element_tree.tag - if '}' in element_tag: - namespace = element_tag[1:element_tag.index('}')] - tag = element_tag[element_tag.index('}') + 1:] - else: - namespace = None - tag = element_tag - extension = ExtensionElement(namespace=namespace, tag=tag) - for key, value in element_tree.attrib.items(): - extension.attributes[key] = value - for child in element_tree: - extension.children.append(_ExtensionElementFromElementTree(child)) - extension.text = element_tree.text - return extension - - -def deprecated(warning=None): - """Decorator to raise warning each time the function is called. - - Args: - warning: The warning message to be displayed as a string (optinoal). - """ - warning = warning or '' - - # This closure is what is returned from the deprecated function. - def mark_deprecated(f): - # The deprecated_function wraps the actual call to f. - @wraps(f) - def deprecated_function(*args, **kwargs): - warnings.warn(warning, DeprecationWarning, stacklevel=2) - return f(*args, **kwargs) - - return deprecated_function - - return mark_deprecated diff --git a/src/gam/atom/http.py b/src/gam/atom/http.py deleted file mode 100644 index df12f297..00000000 --- a/src/gam/atom/http.py +++ /dev/null @@ -1,354 +0,0 @@ -# -# Copyright (C) 2008 Google Inc. -# -# Licensed under the Apache License 2.0; - - - -"""HttpClients in this module use httplib to make HTTP requests. - -This module make HTTP requests based on httplib, but there are environments -in which an httplib based approach will not work (if running in Google App -Engine for example). In those cases, higher level classes (like AtomService -and GDataService) can swap out the HttpClient to transparently use a -different mechanism for making HTTP requests. - - HttpClient: Contains a request method which performs an HTTP call to the - server. - - ProxiedHttpClient: Contains a request method which connects to a proxy using - settings stored in operating system environment variables then - performs an HTTP call to the endpoint server. -""" - -# __author__ = 'api.jscudder (Jeff Scudder)' - -import base64 -import http.client -import os -import socket - -import atom.http_core -import atom.http_interface -import atom.url - -ssl_imported = False -ssl = None -try: - import ssl - - ssl_imported = True -except ImportError: - pass - - -class ProxyError(atom.http_interface.Error): - pass - - -class TestConfigurationError(Exception): - pass - - -DEFAULT_CONTENT_TYPE = 'application/atom+xml' - - -class HttpClient(atom.http_interface.GenericHttpClient): - # Added to allow old v1 HttpClient objects to use the new - # http_code.HttpClient. Used in unit tests to inject a mock client. - v2_http_client = None - - def __init__(self, headers=None): - self.debug = False - self.headers = headers or {} - - def request(self, operation, url, data=None, headers=None): - """Performs an HTTP call to the server, supports GET, POST, PUT, and - DELETE. - - Usage example, perform and HTTP GET on http://www.google.com/: - import atom.http - client = atom.http.HttpClient() - http_response = client.request('GET', 'http://www.google.com/') - - Args: - operation: str The HTTP operation to be performed. This is usually one - of 'GET', 'POST', 'PUT', or 'DELETE' - data: filestream, list of parts, or other object which can be converted - to a string. Should be set to None when performing a GET or DELETE. - If data is a file-like object which can be read, this method will - read a chunk of 100K bytes at a time and send them. - If the data is a list of parts to be sent, each part will be - evaluated and sent. - url: The full URL to which the request should be sent. Can be a string - or atom.url.Url. - headers: dict of strings. HTTP headers which should be sent - in the request. - """ - all_headers = self.headers.copy() - if headers: - all_headers.update(headers) - - # If the list of headers does not include a Content-Length, attempt to - # calculate it based on the data object. - if data and 'Content-Length' not in all_headers: - if isinstance(data, (str,)): - all_headers['Content-Length'] = str(len(data)) - else: - raise atom.http_interface.ContentLengthRequired('Unable to calculate ' - 'the length of the data parameter. Specify a value for ' - 'Content-Length') - - # Set the content type to the default value if none was set. - if 'Content-Type' not in all_headers: - all_headers['Content-Type'] = DEFAULT_CONTENT_TYPE - - if self.v2_http_client is not None: - http_request = atom.http_core.HttpRequest(method=operation) - atom.http_core.Uri.parse_uri(str(url)).modify_request(http_request) - http_request.headers = all_headers - if data: - http_request._body_parts.append(data) - return self.v2_http_client.request(http_request=http_request) - - if not isinstance(url, atom.url.Url): - if isinstance(url, str): - url = atom.url.parse_url(url) - else: - raise atom.http_interface.UnparsableUrlObject('Unable to parse url parameter because it was not a string or atom.url.Url') - - connection = self._prepare_connection(url, all_headers) - - if self.debug: - connection.debuglevel = 1 - - connection.putrequest(operation, self._get_access_url(url), skip_host=True) - - if url.port is not None: - connection.putheader('Host', '%s:%s' % (url.host, url.port)) - else: - connection.putheader('Host', url.host) - - # Overcome a bug in Python 2.4 and 2.5 - # httplib.HTTPConnection.putrequest adding - # HTTP request header 'Host: www.google.com:443' instead of - # 'Host: www.google.com', and thus resulting the error message - # 'Token invalid - AuthSub token has wrong scope' in the HTTP response. - if (url.protocol == 'https' and int(url.port or 443) == 443 and - hasattr(connection, '_buffer') and - isinstance(connection._buffer, list)): - header_line = 'Host: %s:443' % url.host - replacement_header_line = 'Host: %s' % url.host - try: - connection._buffer[connection._buffer.index(header_line)] = ( - replacement_header_line) - except ValueError: # header_line missing from connection._buffer - pass - - # Send the HTTP headers. - for header_name in all_headers: - connection.putheader(header_name, all_headers[header_name]) - connection.endheaders() - - # If there is data, send it in the request. - if data: - if isinstance(data, list): - for data_part in data: - _send_data_part(data_part, connection) - else: - _send_data_part(data, connection) - - # Return the HTTP Response from the server. - return connection.getresponse() - - def _prepare_connection(self, url, headers): - if not isinstance(url, atom.url.Url): - if isinstance(url, (str,)): - url = atom.url.parse_url(url) - else: - raise atom.http_interface.UnparsableUrlObject('Unable to parse url ' - 'parameter because it was not a string or atom.url.Url') - if url.protocol == 'https': - if not url.port: - return http.client.HTTPSConnection(url.host) - return http.client.HTTPSConnection(url.host, int(url.port)) - else: - if not url.port: - return http.client.HTTPConnection(url.host) - return http.client.HTTPConnection(url.host, int(url.port)) - - def _get_access_url(self, url): - return url.to_string() - - -class ProxiedHttpClient(HttpClient): - """Performs an HTTP request through a proxy. - - The proxy settings are obtained from enviroment variables. The URL of the - proxy server is assumed to be stored in the environment variables - 'https_proxy' and 'http_proxy' respectively. If the proxy server requires - a Basic Auth authorization header, the username and password are expected to - be in the 'proxy-username' or 'proxy_username' variable and the - 'proxy-password' or 'proxy_password' variable, or in 'http_proxy' or - 'https_proxy' as "protocol://[username:password@]host:port". - - After connecting to the proxy server, the request is completed as in - HttpClient.request. - """ - - def _prepare_connection(self, url, headers): - proxy_settings = os.environ.get('%s_proxy' % url.protocol) - if not proxy_settings: - # The request was HTTP or HTTPS, but there was no appropriate proxy set. - return HttpClient._prepare_connection(self, url, headers) - else: - proxy_auth = _get_proxy_auth(proxy_settings) - proxy_netloc = _get_proxy_net_location(proxy_settings) - if url.protocol == 'https': - # Set any proxy auth headers - if proxy_auth: - proxy_auth = 'Proxy-Authorization: %s' % proxy_auth - - # Construct the proxy connect command. - port = url.port - if not port: - port = '443' - proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (url.host, port) - - # Set the user agent to send to the proxy - if headers and 'User-Agent' in headers: - user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent']) - else: - user_agent = 'User-Agent: python\r\n' - - proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent) - - # Find the proxy host and port. - proxy_url = atom.url.parse_url(proxy_netloc) - if not proxy_url.port: - proxy_url.port = '80' - - # Connect to the proxy server, very simple recv and error checking - p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - p_sock.connect((proxy_url.host, int(proxy_url.port))) - #p_sock.sendall(proxy_pieces) - p_sock.sendall(proxy_pieces.encode('utf-8')) - response = '' - - # Wait for the full response. - while response.find("\r\n\r\n") == -1: - #response += p_sock.recv(8192) - response += p_sock.recv(8192).decode('utf-8') - - p_status = response.split()[1] - if p_status != str(200): - raise ProxyError('Error status=%s' % str(p_status)) - - # Trivial setup for ssl socket. - sslobj = None - if ssl_imported: - context = ssl.SSLContext(ssl.PROTOCOL_TLS) - context.minimum_version = ssl.TLSVersion.TLSv1_2 - sslobj = context.wrap_socket(p_sock, server_hostname=url.host) - else: - sock_ssl = socket.ssl(p_sock, None, None) - sslobj = http.client.FakeSocket(p_sock, sock_ssl) - - # Initalize httplib and replace with the proxy socket. - connection = http.client.HTTPConnection(proxy_url.host) - connection.sock = sslobj - return connection - else: - # If protocol was not https. - # Find the proxy host and port. - proxy_url = atom.url.parse_url(proxy_netloc) - if not proxy_url.port: - proxy_url.port = '80' - - if proxy_auth: - headers['Proxy-Authorization'] = proxy_auth.strip() - - return http.client.HTTPConnection(proxy_url.host, int(proxy_url.port)) - - def _get_access_url(self, url): - return url.to_string() - - -def _get_proxy_auth(proxy_settings): - """Returns proxy authentication string for header. - - Will check environment variables for proxy authentication info, starting with - proxy(_/-)username and proxy(_/-)password before checking the given - proxy_settings for a [protocol://]username:password@host[:port] string. - - Args: - proxy_settings: String from http_proxy or https_proxy environment variable. - - Returns: - Authentication string for proxy, or empty string if no proxy username was - found. - """ - proxy_username = None - proxy_password = None - - proxy_username = os.environ.get('proxy-username') - if not proxy_username: - proxy_username = os.environ.get('proxy_username') - proxy_password = os.environ.get('proxy-password') - if not proxy_password: - proxy_password = os.environ.get('proxy_password') - - if not proxy_username: - if '@' in proxy_settings: - protocol_and_proxy_auth = proxy_settings.split('@')[0].split(':') - if len(protocol_and_proxy_auth) == 3: - # 3 elements means we have [, //, ] - proxy_username = protocol_and_proxy_auth[1].lstrip('/') - proxy_password = protocol_and_proxy_auth[2] - elif len(protocol_and_proxy_auth) == 2: - # 2 elements means we have [, ] - proxy_username = protocol_and_proxy_auth[0] - proxy_password = protocol_and_proxy_auth[1] - if proxy_username: - user_auth = base64.b64encode(('%s:%s' % (proxy_username, proxy_password)).encode('utf-8')) - return 'Basic %s\r\n' % (user_auth.strip().decode('utf-8')) - else: - return '' - - -def _get_proxy_net_location(proxy_settings): - """Returns proxy host and port. - - Args: - proxy_settings: String from http_proxy or https_proxy environment variable. - Must be in the form of protocol://[username:password@]host:port - - Returns: - String in the form of protocol://host:port - """ - if '@' in proxy_settings: - protocol = proxy_settings.split(':')[0] - netloc = proxy_settings.split('@')[1] - return '%s://%s' % (protocol, netloc) - else: - return proxy_settings - - -def _send_data_part(data, connection): - if isinstance(data, (str,)): - connection.send(data) - return - # Check to see if data is a file-like object that has a read method. - elif hasattr(data, 'read'): - # Read the file and send it a chunk at a time. - while 1: - binarydata = data.read(100000) - if binarydata == b'': break - connection.send(binarydata) - return - else: - # The data object was not a file. - # Try to convert to a string and send the data. - #connection.send(str(data)) - connection.send(str(data).encode('utf-8')) - return diff --git a/src/gam/atom/http_core.py b/src/gam/atom/http_core.py deleted file mode 100644 index 875bb1e7..00000000 --- a/src/gam/atom/http_core.py +++ /dev/null @@ -1,599 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2009 Google Inc. -# -# Licensed under the Apache License 2.0; -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# This module is used for version 2 of the Google Data APIs. -# TODO: add proxy handling. - - -# __author__ = 'j.s@google.com (Jeff Scudder)' - -import http.client -import io -import os -import urllib.error -import urllib.parse -import urllib.request - -ssl = None -try: - import ssl -except ImportError: - pass - - -class Error(Exception): - pass - - -class UnknownSize(Error): - pass - - -class ProxyError(Error): - pass - - -MIME_BOUNDARY = 'END_OF_PART' - - -def get_headers(http_response): - """Retrieves all HTTP headers from an HTTP response from the server. - - This method is provided for backwards compatibility for Python2.2 and 2.3. - The httplib.HTTPResponse object in 2.2 and 2.3 does not have a getheaders - method so this function will use getheaders if available, but if not it - will retrieve a few using getheader. - """ - if hasattr(http_response, 'getheaders'): - return http_response.getheaders() - else: - headers = [] - for header in ( - 'location', 'content-type', 'content-length', 'age', 'allow', - 'cache-control', 'content-location', 'content-encoding', 'date', - 'etag', 'expires', 'last-modified', 'pragma', 'server', - 'set-cookie', 'transfer-encoding', 'vary', 'via', 'warning', - 'www-authenticate', 'gdata-version'): - value = http_response.getheader(header, None) - if value is not None: - headers.append((header, value)) - return headers - - -class HttpRequest(object): - """Contains all of the parameters for an HTTP 1.1 request. - - The HTTP headers are represented by a dictionary, and it is the - responsibility of the user to ensure that duplicate field names are combined - into one header value according to the rules in section 4.2 of RFC 2616. - """ - method = None - uri = None - - def __init__(self, uri=None, method=None, headers=None): - """Construct an HTTP request. - - Args: - uri: The full path or partial path as a Uri object or a string. - method: The HTTP method for the request, examples include 'GET', 'POST', - etc. - headers: dict of strings The HTTP headers to include in the request. - """ - self.headers = headers or {} - self._body_parts = [] - if method is not None: - self.method = method - if isinstance(uri, str): - uri = Uri.parse_uri(uri) - self.uri = uri or Uri() - - def add_body_part(self, data, mime_type, size=None): - """Adds data to the HTTP request body. - - If more than one part is added, this is assumed to be a mime-multipart - request. This method is designed to create MIME 1.0 requests as specified - in RFC 1341. - - Args: - data: str or a file-like object containing a part of the request body. - mime_type: str The MIME type describing the data - size: int Required if the data is a file like object. If the data is a - string, the size is calculated so this parameter is ignored. - """ - if hasattr(data, '__len__'): - size = len(data) - if size is None: - # TODO: support chunked transfer if some of the body is of unknown size. - raise UnknownSize('Each part of the body must have a known size.') - if 'Content-Length' in self.headers: - content_length = int(self.headers['Content-Length']) - else: - content_length = 0 - # If this is the first part added to the body, then this is not a multipart - # request. - if len(self._body_parts) == 0: - self.headers['Content-Type'] = mime_type - content_length = size - self._body_parts.append(data) - elif len(self._body_parts) == 1: - # This is the first member in a mime-multipart request, so change the - # _body_parts list to indicate a multipart payload. - self._body_parts.insert(0, 'Media multipart posting') - boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) - content_length += len(boundary_string) + size - self._body_parts.insert(1, boundary_string) - content_length += len('Media multipart posting') - # Put the content type of the first part of the body into the multipart - # payload. - original_type_string = 'Content-Type: %s\r\n\r\n' % ( - self.headers['Content-Type'],) - self._body_parts.insert(2, original_type_string) - content_length += len(original_type_string) - boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) - self._body_parts.append(boundary_string) - content_length += len(boundary_string) - # Change the headers to indicate this is now a mime multipart request. - self.headers['Content-Type'] = 'multipart/related; boundary="%s"' % ( - MIME_BOUNDARY,) - self.headers['MIME-version'] = '1.0' - # Include the mime type of this part. - type_string = 'Content-Type: %s\r\n\r\n' % (mime_type) - self._body_parts.append(type_string) - content_length += len(type_string) - self._body_parts.append(data) - ending_boundary_string = '\r\n--%s--' % (MIME_BOUNDARY,) - self._body_parts.append(ending_boundary_string) - content_length += len(ending_boundary_string) - else: - # This is a mime multipart request. - boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,) - self._body_parts.insert(-1, boundary_string) - content_length += len(boundary_string) + size - # Include the mime type of this part. - type_string = 'Content-Type: %s\r\n\r\n' % (mime_type) - self._body_parts.insert(-1, type_string) - content_length += len(type_string) - self._body_parts.insert(-1, data) - self.headers['Content-Length'] = str(content_length) - - # I could add an "append_to_body_part" method as well. - - AddBodyPart = add_body_part - - def add_form_inputs(self, form_data, - mime_type='application/x-www-form-urlencoded'): - """Form-encodes and adds data to the request body. - - Args: - form_data: dict or sequnce or two member tuples which contains the - form keys and values. - mime_type: str The MIME type of the form data being sent. Defaults - to 'application/x-www-form-urlencoded'. - """ - body = urllib.parse.urlencode(form_data) - self.add_body_part(body, bytes(mime_type, 'ascii')) - - AddFormInputs = add_form_inputs - - def _copy(self): - """Creates a deep copy of this request.""" - copied_uri = Uri(self.uri.scheme, self.uri.host, self.uri.port, - self.uri.path, self.uri.query.copy()) - new_request = HttpRequest(uri=copied_uri, method=self.method, - headers=self.headers.copy()) - new_request._body_parts = self._body_parts[:] - return new_request - - def _dump(self): - """Converts to a printable string for debugging purposes. - - In order to preserve the request, it does not read from file-like objects - in the body. - """ - output = 'HTTP Request\n method: %s\n url: %s\n headers:\n' % ( - self.method, str(self.uri)) - for header, value in self.headers.items(): - output += ' %s: %s\n' % (header, value) - output += ' body sections:\n' - i = 0 - for part in self._body_parts: - if isinstance(part, str): - output += ' %s: %s\n' % (i, part) - else: - output += ' %s: \n' % i - i += 1 - return output - - -def _apply_defaults(http_request): - if http_request.uri.scheme is None: - if http_request.uri.port == 443: - http_request.uri.scheme = 'https' - else: - http_request.uri.scheme = 'http' - - -class Uri(object): - """A URI as used in HTTP 1.1""" - scheme = None - host = None - port = None - path = None - - def __init__(self, scheme=None, host=None, port=None, path=None, query=None): - """Constructor for a URI. - - Args: - scheme: str This is usually 'http' or 'https'. - host: str The host name or IP address of the desired server. - post: int The server's port number. - path: str The path of the resource following the host. This begins with - a /, example: '/calendar/feeds/default/allcalendars/full' - query: dict of strings The URL query parameters. The keys and values are - both escaped so this dict should contain the unescaped values. - For example {'my key': 'val', 'second': '!!!'} will become - '?my+key=val&second=%21%21%21' which is appended to the path. - """ - self.query = query or {} - if scheme is not None: - self.scheme = scheme - if host is not None: - self.host = host - if port is not None: - self.port = port - if path: - self.path = path - - def _get_query_string(self): - param_pairs = [] - for key, value in self.query.items(): - quoted_key = urllib.parse.quote_plus(str(key)) - if value is None: - param_pairs.append(quoted_key) - else: - quoted_value = urllib.parse.quote_plus(str(value)) - param_pairs.append('%s=%s' % (quoted_key, quoted_value)) - return '&'.join(param_pairs) - - def _get_relative_path(self): - """Returns the path with the query parameters escaped and appended.""" - param_string = self._get_query_string() - if self.path is None: - path = '/' - else: - path = self.path - if param_string: - return '?'.join([path, param_string]) - else: - return path - - def _to_string(self): - if self.scheme is None and self.port == 443: - scheme = 'https' - elif self.scheme is None: - scheme = 'http' - else: - scheme = self.scheme - if self.path is None: - path = '/' - else: - path = self.path - if self.port is None: - return '%s://%s%s' % (scheme, self.host, self._get_relative_path()) - else: - return '%s://%s:%s%s' % (scheme, self.host, str(self.port), - self._get_relative_path()) - - def __str__(self): - return self._to_string() - - def modify_request(self, http_request=None): - """Sets HTTP request components based on the URI.""" - if http_request is None: - http_request = HttpRequest() - if http_request.uri is None: - http_request.uri = Uri() - # Determine the correct scheme. - if self.scheme: - http_request.uri.scheme = self.scheme - if self.port: - http_request.uri.port = self.port - if self.host: - http_request.uri.host = self.host - # Set the relative uri path - if self.path: - http_request.uri.path = self.path - if self.query: - http_request.uri.query = self.query.copy() - return http_request - - ModifyRequest = modify_request - - def parse_uri(uri_string): - """Creates a Uri object which corresponds to the URI string. - - This method can accept partial URIs, but it will leave missing - members of the Uri unset. - """ - parts = urllib.parse.urlparse(uri_string) - uri = Uri() - if parts[0]: - uri.scheme = parts[0] - if parts[1]: - host_parts = parts[1].split(':') - if host_parts[0]: - uri.host = host_parts[0] - if len(host_parts) > 1: - uri.port = int(host_parts[1]) - if parts[2]: - uri.path = parts[2] - if parts[4]: - param_pairs = parts[4].split('&') - for pair in param_pairs: - pair_parts = pair.split('=') - if len(pair_parts) > 1: - uri.query[urllib.parse.unquote_plus(pair_parts[0])] = ( - urllib.parse.unquote_plus(pair_parts[1])) - elif len(pair_parts) == 1: - uri.query[urllib.parse.unquote_plus(pair_parts[0])] = None - return uri - - parse_uri = staticmethod(parse_uri) - - ParseUri = parse_uri - - -parse_uri = Uri.parse_uri - -ParseUri = Uri.parse_uri - - -class HttpResponse(object): - status = None - reason = None - _body = None - - def __init__(self, status=None, reason=None, headers=None, body=None): - self._headers = headers or {} - if status is not None: - self.status = status - if reason is not None: - self.reason = reason - if body is not None: - if hasattr(body, 'read'): - self._body = body - else: - self._body = io.StringIO(body) - - def getheader(self, name, default=None): - if name in self._headers: - return self._headers[name] - else: - return default - - def getheaders(self): - return self._headers - - def read(self, amt=None): - if self._body is None: - return None - if not amt: - return self._body.read() - else: - return self._body.read(amt) - - -def _dump_response(http_response): - """Converts to a string for printing debug messages. - - Does not read the body since that may consume the content. - """ - output = 'HttpResponse\n status: %s\n reason: %s\n headers:' % ( - http_response.status, http_response.reason) - headers = get_headers(http_response) - if isinstance(headers, dict): - for header, value in headers.items(): - output += ' %s: %s\n' % (header, value) - else: - for pair in headers: - output += ' %s: %s\n' % (pair[0], pair[1]) - return output - - -class HttpClient(object): - """Performs HTTP requests using httplib.""" - debug = None - - def request(self, http_request): - return self._http_request(http_request.method, http_request.uri, - http_request.headers, http_request._body_parts) - - Request = request - - def _get_connection(self, uri, headers=None): - """Opens a socket connection to the server to set up an HTTP request. - - Args: - uri: The full URL for the request as a Uri object. - headers: A dict of string pairs containing the HTTP headers for the - request. - """ - connection = None - if uri.scheme == 'https': - if not uri.port: - connection = http.client.HTTPSConnection(uri.host) - else: - connection = http.client.HTTPSConnection(uri.host, int(uri.port)) - else: - if not uri.port: - connection = http.client.HTTPConnection(uri.host) - else: - connection = http.client.HTTPConnection(uri.host, int(uri.port)) - return connection - - def _http_request(self, method, uri, headers=None, body_parts=None): - """Makes an HTTP request using httplib. - - Args: - method: str example: 'GET', 'POST', 'PUT', 'DELETE', etc. - uri: str or atom.http_core.Uri - headers: dict of strings mapping to strings which will be sent as HTTP - headers in the request. - body_parts: list of strings, objects with a read method, or objects - which can be converted to strings using str. Each of these - will be sent in order as the body of the HTTP request. - """ - if isinstance(uri, str): - uri = Uri.parse_uri(uri) - - connection = self._get_connection(uri, headers=headers) - - if self.debug: - connection.debuglevel = 1 - - if connection.host != uri.host: - connection.putrequest(method, str(uri)) - else: - connection.putrequest(method, uri._get_relative_path()) - - # Overcome a bug in Python 2.4 and 2.5 - # httplib.HTTPConnection.putrequest adding - # HTTP request header 'Host: www.google.com:443' instead of - # 'Host: www.google.com', and thus resulting the error message - # 'Token invalid - AuthSub token has wrong scope' in the HTTP response. - if (uri.scheme == 'https' and int(uri.port or 443) == 443 and - hasattr(connection, '_buffer') and - isinstance(connection._buffer, list)): - header_line = 'Host: %s:443' % uri.host - replacement_header_line = 'Host: %s' % uri.host - try: - connection._buffer[connection._buffer.index(header_line)] = ( - replacement_header_line) - except ValueError: # header_line missing from connection._buffer - pass - - # Send the HTTP headers. - for header_name, value in headers.items(): - connection.putheader(header_name, value) - connection.endheaders() - - # If there is data, send it in the request. - if body_parts and [x for x in body_parts if x != '']: - for part in body_parts: - _send_data_part(part, connection) - - # Return the HTTP Response from the server. - return connection.getresponse() - - -def _send_data_part(data, connection): - if isinstance(data, str): - # I might want to just allow str, not unicode. - connection.send(data.encode()) - # Check to see if data is a file-like object that has a read method. - elif hasattr(data, 'read'): - # Read the file and send it a chunk at a time. - while 1: - binarydata = data.read(100000) - if binarydata == '': break - connection.send(binarydata) - else: - # The data object was not a file. - # Try to convert to a string and send the data. - connection.send(data) - - -class ProxiedHttpClient(HttpClient): - def _get_connection(self, uri, headers=None): - # Check to see if there are proxy settings required for this request. - proxy = None - if uri.scheme == 'https': - proxy = os.environ.get('https_proxy') - elif uri.scheme == 'http': - proxy = os.environ.get('http_proxy') - if not proxy: - return HttpClient._get_connection(self, uri, headers=headers) - # Now we have the URL of the appropriate proxy server. - # Get a username and password for the proxy if required. - proxy_auth = _get_proxy_auth() - if uri.scheme == 'https': - import socket - if proxy_auth: - proxy_auth = 'Proxy-Authorization: %s' % proxy_auth - # Construct the proxy connect command. - port = uri.port - if not port: - port = 443 - proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (uri.host, port) - # Set the user agent to send to the proxy - user_agent = '' - if headers and 'User-Agent' in headers: - user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent']) - proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent) - # Find the proxy host and port. - proxy_uri = Uri.parse_uri(proxy) - if not proxy_uri.port: - proxy_uri.port = '80' - # Connect to the proxy server, very simple recv and error checking - p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - p_sock.connect((proxy_uri.host, int(proxy_uri.port))) - #p_sock.sendall(proxy_pieces) - p_sock.sendall(proxy_pieces.encode('utf-8')) - response = '' - # Wait for the full response. - while response.find("\r\n\r\n") == -1: - #response += p_sock.recv(8192) - response += p_sock.recv(8192).decode('utf-8') - p_status = response.split()[1] - if p_status != str(200): - raise ProxyError('Error status=%s' % str(p_status)) - # Trivial setup for ssl socket. - sslobj = None - if ssl is not None: - context = ssl.SSLContext(ssl.PROTOCOL_TLS) - context.minimum_version = ssl.TLSVersion.TLSv1_2 - sslobj = context.wrap_socket(p_sock, server_hostname=uri.host) - else: - sock_ssl = socket.ssl(p_sock, None, None) - sslobj = http.client.FakeSocket(p_sock, sock_ssl) - # Initalize httplib and replace with the proxy socket. - connection = http.client.HTTPConnection(proxy_uri.host) - connection.sock = sslobj - return connection - elif uri.scheme == 'http': - proxy_uri = Uri.parse_uri(proxy) - if not proxy_uri.port: - proxy_uri.port = '80' - if proxy_auth: - headers['Proxy-Authorization'] = proxy_auth.strip() - return http.client.HTTPConnection(proxy_uri.host, int(proxy_uri.port)) - return None - - -def _get_proxy_auth(): - import base64 - proxy_username = os.environ.get('proxy-username') - if not proxy_username: - proxy_username = os.environ.get('proxy_username') - proxy_password = os.environ.get('proxy-password') - if not proxy_password: - proxy_password = os.environ.get('proxy_password') - if proxy_username: - user_auth = base64.b64encode(('%s:%s' % (proxy_username, proxy_password)).encode('utf-8')) - return 'Basic %s\r\n' % (user_auth.strip().decode('utf-8')) - else: - return '' diff --git a/src/gam/atom/http_interface.py b/src/gam/atom/http_interface.py deleted file mode 100644 index 5fdc9052..00000000 --- a/src/gam/atom/http_interface.py +++ /dev/null @@ -1,144 +0,0 @@ -# -# Copyright (C) 2008 Google Inc. -# -# Licensed under the Apache License 2.0; - - -"""This module provides a common interface for all HTTP requests. - - HttpResponse: Represents the server's response to an HTTP request. Provides - an interface identical to httplib.HTTPResponse which is the response - expected from higher level classes which use HttpClient.request. - - GenericHttpClient: Provides an interface (superclass) for an object - responsible for making HTTP requests. Subclasses of this object are - used in AtomService and GDataService to make requests to the server. By - changing the http_client member object, the AtomService is able to make - HTTP requests using different logic (for example, when running on - Google App Engine, the http_client makes requests using the App Engine - urlfetch API). -""" - -# __author__ = 'api.jscudder (Jeff Scudder)' - -import io - -USER_AGENT = '%s GData-Python/2.0.18' - - -class Error(Exception): - pass - - -class UnparsableUrlObject(Error): - pass - - -class ContentLengthRequired(Error): - pass - - -class HttpResponse(object): - def __init__(self, body=None, status=None, reason=None, headers=None): - """Constructor for an HttpResponse object. - - HttpResponse represents the server's response to an HTTP request from - the client. The HttpClient.request method returns a httplib.HTTPResponse - object and this HttpResponse class is designed to mirror the interface - exposed by httplib.HTTPResponse. - - Args: - body: A file like object, with a read() method. The body could also - be a string, and the constructor will wrap it so that - HttpResponse.read(self) will return the full string. - status: The HTTP status code as an int. Example: 200, 201, 404. - reason: The HTTP status message which follows the code. Example: - OK, Created, Not Found - headers: A dictionary containing the HTTP headers in the server's - response. A common header in the response is Content-Length. - """ - if body: - if hasattr(body, 'read'): - self._body = body - else: - self._body = io.StringIO(body) - else: - self._body = None - if status is not None: - self.status = int(status) - else: - self.status = None - self.reason = reason - self._headers = headers or {} - - def getheader(self, name, default=None): - if name in self._headers: - return self._headers[name] - else: - return default - - def read(self, amt=None): - if not amt: - return self._body.read() - else: - return self._body.read(amt) - - -class GenericHttpClient(object): - debug = False - - def __init__(self, http_client, headers=None): - """ - - Args: - http_client: An object which provides a request method to make an HTTP - request. The request method in GenericHttpClient performs a - call-through to the contained HTTP client object. - headers: A dictionary containing HTTP headers which should be included - in every HTTP request. Common persistent headers include - 'User-Agent'. - """ - self.http_client = http_client - self.headers = headers or {} - - def request(self, operation, url, data=None, headers=None): - all_headers = self.headers.copy() - if headers: - all_headers.update(headers) - return self.http_client.request(operation, url, data=data, - headers=all_headers) - - def get(self, url, headers=None): - return self.request('GET', url, headers=headers) - - def post(self, url, data, headers=None): - return self.request('POST', url, data=data, headers=headers) - - def put(self, url, data, headers=None): - return self.request('PUT', url, data=data, headers=headers) - - def delete(self, url, headers=None): - return self.request('DELETE', url, headers=headers) - - -class GenericToken(object): - """Represents an Authorization token to be added to HTTP requests. - - Some Authorization headers included calculated fields (digital - signatures for example) which are based on the parameters of the HTTP - request. Therefore the token is responsible for signing the request - and adding the Authorization header. - """ - - def perform_request(self, http_client, operation, url, data=None, - headers=None): - """For the GenericToken, no Authorization token is set.""" - return http_client.request(operation, url, data=data, headers=headers) - - def valid_for_scope(self, url): - """Tells the caller if the token authorizes access to the desired URL. - - Since the generic token doesn't add an auth header, it is not valid for - any scope. - """ - return False diff --git a/src/gam/atom/service.py b/src/gam/atom/service.py deleted file mode 100644 index 45a4516c..00000000 --- a/src/gam/atom/service.py +++ /dev/null @@ -1,723 +0,0 @@ -# -# Copyright (C) 2006, 2007, 2008 Google Inc. -# -# Licensed under the Apache License 2.0; - - - -"""AtomService provides CRUD ops. in line with the Atom Publishing Protocol. - - AtomService: Encapsulates the ability to perform insert, update and delete - operations with the Atom Publishing Protocol on which GData is - based. An instance can perform query, insertion, deletion, and - update. - - HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request - to the specified end point. An AtomService object or a subclass can be - used to specify information about the request. -""" - -# __author__ = 'api.jscudder (Jeff Scudder)' - -import base64 -import http.client -import os -import socket -import urllib.error -import urllib.parse -import urllib.request -import warnings - -import atom.http -import atom.http_interface -import atom.token_store -import atom.url - -import lxml.etree as ElementTree -import atom - - -class AtomService(object): - """Performs Atom Publishing Protocol CRUD operations. - - The AtomService contains methods to perform HTTP CRUD operations. - """ - - # Default values for members - port = 80 - ssl = False - # Set the current_token to force the AtomService to use this token - # instead of searching for an appropriate token in the token_store. - current_token = None - auto_store_tokens = True - auto_set_current_token = True - - def _get_override_token(self): - return self.current_token - - def _set_override_token(self, token): - self.current_token = token - - override_token = property(_get_override_token, _set_override_token) - - # @atom.v1_deprecated('Please use atom.client.AtomPubClient instead.') - def __init__(self, server=None, additional_headers=None, - application_name='', http_client=None, token_store=None): - """Creates a new AtomService client. - - Args: - server: string (optional) The start of a URL for the server - to which all operations should be directed. Example: - 'www.google.com' - additional_headers: dict (optional) Any additional HTTP headers which - should be included with CRUD operations. - http_client: An object responsible for making HTTP requests using a - request method. If none is provided, a new instance of - atom.http.ProxiedHttpClient will be used. - token_store: Keeps a collection of authorization tokens which can be - applied to requests for a specific URLs. Critical methods are - find_token based on a URL (atom.url.Url or a string), add_token, - and remove_token. - """ - self.http_client = http_client or atom.http.ProxiedHttpClient() - self.token_store = token_store or atom.token_store.TokenStore() - self.server = server - self.additional_headers = additional_headers or {} - self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( - application_name,) - # If debug is True, the HTTPConnection will display debug information - self._set_debug(False) - - __init__ = atom.v1_deprecated( - 'Please use atom.client.AtomPubClient instead.')( - __init__) - - def _get_debug(self): - return self.http_client.debug - - def _set_debug(self, value): - self.http_client.debug = value - - debug = property(_get_debug, _set_debug, - doc='If True, HTTP debug information is printed.') - - def use_basic_auth(self, username, password, scopes=None): - if username is not None and password is not None: - if scopes is None: - scopes = [atom.token_store.SCOPE_ALL] - base_64_string = base64.encodestring('%s:%s' % (username, password)) - token = BasicAuthToken('Basic %s' % base_64_string.strip(), - scopes=[atom.token_store.SCOPE_ALL]) - if self.auto_set_current_token: - self.current_token = token - if self.auto_store_tokens: - return self.token_store.add_token(token) - return True - return False - - def UseBasicAuth(self, username, password, for_proxy=False): - """Sets an Authenticaiton: Basic HTTP header containing plaintext. - - Deprecated, use use_basic_auth instead. - - The username and password are base64 encoded and added to an HTTP header - which will be included in each request. Note that your username and - password are sent in plaintext. - - Args: - username: str - password: str - """ - self.use_basic_auth(username, password) - - # @atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.') - def request(self, operation, url, data=None, headers=None, - url_params=None): - if isinstance(url, str): - if url.startswith('http:') and self.ssl: - # Force all requests to be https if self.ssl is True. - url = atom.url.parse_url('https:' + url[5:]) - elif not url.startswith('http') and self.ssl: - url = atom.url.parse_url('https://%s%s' % (self.server, url)) - elif not url.startswith('http'): - url = atom.url.parse_url('http://%s%s' % (self.server, url)) - else: - url = atom.url.parse_url(url) - - if url_params: - for name, value in url_params.items(): - url.params[name] = value - - all_headers = self.additional_headers.copy() - if headers: - all_headers.update(headers) - - # If the list of headers does not include a Content-Length, attempt to - # calculate it based on the data object. - if data and 'Content-Length' not in all_headers: - content_length = CalculateDataLength(data) - if content_length: - all_headers['Content-Length'] = str(content_length) - - # Find an Authorization token for this URL if one is available. - if self.override_token: - auth_token = self.override_token - else: - auth_token = self.token_store.find_token(url) - return auth_token.perform_request(self.http_client, operation, url, - data=data, headers=all_headers) - - request = atom.v1_deprecated( - 'Please use atom.client.AtomPubClient for requests.')( - request) - - # CRUD operations - def Get(self, uri, extra_headers=None, url_params=None, escape_params=True): - """Query the APP server with the given URI - - The uri is the portion of the URI after the server value - (server example: 'www.google.com'). - - Example use: - To perform a query against Google Base, set the server to - 'base.google.com' and set the uri to '/base/feeds/...', where ... is - your query. For example, to find snippets for all digital cameras uri - should be set to: '/base/feeds/snippets?bq=digital+camera' - - Args: - uri: string The query in the form of a URI. Example: - '/base/feeds/snippets?bq=digital+camera'. - extra_headers: dicty (optional) Extra HTTP headers to be included - in the GET request. These headers are in addition to - those stored in the client's additional_headers property. - The client automatically sets the Content-Type and - Authorization headers. - url_params: dict (optional) Additional URL parameters to be included - in the query. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - - Returns: - httplib.HTTPResponse The server's response to the GET request. - """ - return self.request('GET', uri, data=None, headers=extra_headers, - url_params=url_params) - - def Post(self, data, uri, extra_headers=None, url_params=None, - escape_params=True, content_type='application/atom+xml'): - """Insert data into an APP server at the given URI. - - Args: - data: string, ElementTree._Element, or something with a __str__ method - The XML to be sent to the uri. - uri: string The location (feed) to which the data should be inserted. - Example: '/base/feeds/items'. - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type, - Authorization, and Content-Length headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - - Returns: - httplib.HTTPResponse Server's response to the POST request. - """ - if extra_headers is None: - extra_headers = {} - if content_type: - extra_headers['Content-Type'] = content_type - return self.request('POST', uri, data=data, headers=extra_headers, - url_params=url_params) - - def Put(self, data, uri, extra_headers=None, url_params=None, - escape_params=True, content_type='application/atom+xml'): - """Updates an entry at the given URI. - - Args: - data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The - XML containing the updated data. - uri: string A URI indicating entry to which the update will be applied. - Example: '/base/feeds/items/ITEM-ID' - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type, - Authorization, and Content-Length headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - - Returns: - httplib.HTTPResponse Server's response to the PUT request. - """ - if extra_headers is None: - extra_headers = {} - if content_type: - extra_headers['Content-Type'] = content_type - return self.request('PUT', uri, data=data, headers=extra_headers, - url_params=url_params) - - def Delete(self, uri, extra_headers=None, url_params=None, - escape_params=True): - """Deletes the entry at the given URI. - - Args: - uri: string The URI of the entry to be deleted. Example: - '/base/feeds/items/ITEM-ID' - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type and - Authorization headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - - Returns: - httplib.HTTPResponse Server's response to the DELETE request. - """ - return self.request('DELETE', uri, data=None, headers=extra_headers, - url_params=url_params) - - -class BasicAuthToken(atom.http_interface.GenericToken): - def __init__(self, auth_header, scopes=None): - """Creates a token used to add Basic Auth headers to HTTP requests. - - Args: - auth_header: str The value for the Authorization header. - scopes: list of str or atom.url.Url specifying the beginnings of URLs - for which this token can be used. For example, if scopes contains - 'http://example.com/foo', then this token can be used for a request to - 'http://example.com/foo/bar' but it cannot be used for a request to - 'http://example.com/baz' - """ - self.auth_header = auth_header - self.scopes = scopes or [] - - def perform_request(self, http_client, operation, url, data=None, - headers=None): - """Sets the Authorization header to the basic auth string.""" - if headers is None: - headers = {'Authorization': self.auth_header} - else: - headers['Authorization'] = self.auth_header - return http_client.request(operation, url, data=data, headers=headers) - - def __str__(self): - return self.auth_header - - def valid_for_scope(self, url): - """Tells the caller if the token authorizes access to the desired URL. - """ - if isinstance(url, str): - url = atom.url.parse_url(url) - for scope in self.scopes: - if scope == atom.token_store.SCOPE_ALL: - return True - if isinstance(scope, str): - scope = atom.url.parse_url(scope) - if scope == url: - return True - # Check the host and the path, but ignore the port and protocol. - elif scope.host == url.host and not scope.path: - return True - elif scope.host == url.host and scope.path and not url.path: - continue - elif scope.host == url.host and url.path.startswith(scope.path): - return True - return False - - -def PrepareConnection(service, full_uri): - """Opens a connection to the server based on the full URI. - - This method is deprecated, instead use atom.http.HttpClient.request. - - Examines the target URI and the proxy settings, which are set as - environment variables, to open a connection with the server. This - connection is used to make an HTTP request. - - Args: - service: atom.AtomService or a subclass. It must have a server string which - represents the server host to which the request should be made. It may also - have a dictionary of additional_headers to send in the HTTP request. - full_uri: str Which is the target relative (lacks protocol and host) or - absolute URL to be opened. Example: - 'https://www.google.com/accounts/ClientLogin' or - 'base/feeds/snippets' where the server is set to www.google.com. - - Returns: - A tuple containing the httplib.HTTPConnection and the full_uri for the - request. - """ - deprecation('calling deprecated function PrepareConnection') - (server, port, ssl, partial_uri) = ProcessUrl(service, full_uri) - if ssl: - # destination is https - proxy = os.environ.get('https_proxy') - if proxy: - (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True) - proxy_username = os.environ.get('proxy-username') - if not proxy_username: - proxy_username = os.environ.get('proxy_username') - proxy_password = os.environ.get('proxy-password') - if not proxy_password: - proxy_password = os.environ.get('proxy_password') - if proxy_username: - user_auth = base64.encodestring('%s:%s' % (proxy_username, - proxy_password)) - proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % ( - user_auth.strip())) - else: - proxy_authorization = '' - proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port) - user_agent = 'User-Agent: %s\r\n' % ( - service.additional_headers['User-Agent']) - proxy_pieces = (proxy_connect + proxy_authorization + user_agent - + '\r\n') - - # now connect, very simple recv and error checking - p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - p_sock.connect((p_server, p_port)) - p_sock.sendall(proxy_pieces) - response = '' - - # Wait for the full response. - while response.find("\r\n\r\n") == -1: - response += p_sock.recv(8192) - - p_status = response.split()[1] - if p_status != str(200): - raise atom.http.ProxyError('Error status=%s' % p_status) - - # Trivial setup for ssl socket. - ssl = socket.ssl(p_sock, None, None) - fake_sock = http.client.FakeSocket(p_sock, ssl) - - # Initalize httplib and replace with the proxy socket. - connection = http.client.HTTPConnection(server) - connection.sock = fake_sock - full_uri = partial_uri - - else: - connection = http.client.HTTPSConnection(server, port) - full_uri = partial_uri - - else: - # destination is http - proxy = os.environ.get('http_proxy') - if proxy: - (p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True) - proxy_username = os.environ.get('proxy-username') - if not proxy_username: - proxy_username = os.environ.get('proxy_username') - proxy_password = os.environ.get('proxy-password') - if not proxy_password: - proxy_password = os.environ.get('proxy_password') - if proxy_username: - UseBasicAuth(service, proxy_username, proxy_password, True) - connection = http.client.HTTPConnection(p_server, p_port) - if not full_uri.startswith("http://"): - if full_uri.startswith("/"): - full_uri = "http://%s%s" % (service.server, full_uri) - else: - full_uri = "http://%s/%s" % (service.server, full_uri) - else: - connection = http.client.HTTPConnection(server, port) - full_uri = partial_uri - - return (connection, full_uri) - - -def UseBasicAuth(service, username, password, for_proxy=False): - """Sets an Authenticaiton: Basic HTTP header containing plaintext. - - Deprecated, use AtomService.use_basic_auth insread. - - The username and password are base64 encoded and added to an HTTP header - which will be included in each request. Note that your username and - password are sent in plaintext. The auth header is added to the - additional_headers dictionary in the service object. - - Args: - service: atom.AtomService or a subclass which has an - additional_headers dict as a member. - username: str - password: str - """ - deprecation('calling deprecated function UseBasicAuth') - base_64_string = base64.encodestring('%s:%s' % (username, password)) - base_64_string = base_64_string.strip() - if for_proxy: - header_name = 'Proxy-Authorization' - else: - header_name = 'Authorization' - service.additional_headers[header_name] = 'Basic %s' % (base_64_string,) - - -def ProcessUrl(service, url, for_proxy=False): - """Processes a passed URL. If the URL does not begin with https?, then - the default value for server is used - - This method is deprecated, use atom.url.parse_url instead. - """ - if not isinstance(url, atom.url.Url): - url = atom.url.parse_url(url) - - server = url.host - ssl = False - port = 80 - - if not server: - if hasattr(service, 'server'): - server = service.server - else: - server = service - if not url.protocol and hasattr(service, 'ssl'): - ssl = service.ssl - if hasattr(service, 'port'): - port = service.port - else: - if url.protocol == 'https': - ssl = True - elif url.protocol == 'http': - ssl = False - if url.port: - port = int(url.port) - elif port == 80 and ssl: - port = 443 - - return (server, port, ssl, url.get_request_uri()) - - -def DictionaryToParamList(url_parameters, escape_params=True): - """Convert a dictionary of URL arguments into a URL parameter string. - - This function is deprcated, use atom.url.Url instead. - - Args: - url_parameters: The dictionaty of key-value pairs which will be converted - into URL parameters. For example, - {'dry-run': 'true', 'foo': 'bar'} - will become ['dry-run=true', 'foo=bar']. - - Returns: - A list which contains a string for each key-value pair. The strings are - ready to be incorporated into a URL by using '&'.join([] + parameter_list) - """ - # Choose which function to use when modifying the query and parameters. - # Use quote_plus when escape_params is true. - transform_op = [str, urllib.parse.quote_plus][bool(escape_params)] - # Create a list of tuples containing the escaped version of the - # parameter-value pairs. - parameter_tuples = [(transform_op(param), transform_op(value)) - for param, value in list((url_parameters or {}).items())] - # Turn parameter-value tuples into a list of strings in the form - # 'PARAMETER=VALUE'. - return ['='.join(x) for x in parameter_tuples] - - -def BuildUri(uri, url_params=None, escape_params=True): - """Converts a uri string and a collection of parameters into a URI. - - This function is deprcated, use atom.url.Url instead. - - Args: - uri: string - url_params: dict (optional) - escape_params: boolean (optional) - uri: string The start of the desired URI. This string can alrady contain - URL parameters. Examples: '/base/feeds/snippets', - '/base/feeds/snippets?bq=digital+camera' - url_parameters: dict (optional) Additional URL parameters to be included - in the query. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - - Returns: - string The URI consisting of the escaped URL parameters appended to the - initial uri string. - """ - # Prepare URL parameters for inclusion into the GET request. - parameter_list = DictionaryToParamList(url_params, escape_params) - - # Append the URL parameters to the URL. - if parameter_list: - if uri.find('?') != -1: - # If there are already URL parameters in the uri string, add the - # parameters after a new & character. - full_uri = '&'.join([uri] + parameter_list) - else: - # The uri string did not have any URL parameters (no ? character) - # so put a ? between the uri and URL parameters. - full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list))) - else: - full_uri = uri - - return full_uri - - -def HttpRequest(service, operation, data, uri, extra_headers=None, - url_params=None, escape_params=True, content_type='application/atom+xml'): - """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE. - - This method is deprecated, use atom.http.HttpClient.request instead. - - Usage example, perform and HTTP GET on http://www.google.com/: - import atom.service - client = atom.service.AtomService() - http_response = client.Get('http://www.google.com/') - or you could set the client.server to 'www.google.com' and use the - following: - client.server = 'www.google.com' - http_response = client.Get('/') - - Args: - service: atom.AtomService object which contains some of the parameters - needed to make the request. The following members are used to - construct the HTTP call: server (str), additional_headers (dict), - port (int), and ssl (bool). - operation: str The HTTP operation to be performed. This is usually one of - 'GET', 'POST', 'PUT', or 'DELETE' - data: ElementTree, filestream, list of parts, or other object which can be - converted to a string. - Should be set to None when performing a GET or PUT. - If data is a file-like object which can be read, this method will read - a chunk of 100K bytes at a time and send them. - If the data is a list of parts to be sent, each part will be evaluated - and sent. - uri: The beginning of the URL to which the request should be sent. - Examples: '/', '/base/feeds/snippets', - '/m8/feeds/contacts/default/base' - extra_headers: dict of strings. HTTP headers which should be sent - in the request. These headers are in addition to those stored in - service.additional_headers. - url_params: dict of strings. Key value pairs to be added to the URL as - URL parameters. For example {'foo':'bar', 'test':'param'} will - become ?foo=bar&test=param. - escape_params: bool default True. If true, the keys and values in - url_params will be URL escaped when the form is constructed - (Special characters converted to %XX form.) - content_type: str The MIME type for the data being sent. Defaults to - 'application/atom+xml', this is only used if data is set. - """ - deprecation('call to deprecated function HttpRequest') - full_uri = BuildUri(uri, url_params, escape_params) - (connection, full_uri) = PrepareConnection(service, full_uri) - - if extra_headers is None: - extra_headers = {} - - # Turn on debug mode if the debug member is set. - if service.debug: - connection.debuglevel = 1 - - connection.putrequest(operation, full_uri) - - # If the list of headers does not include a Content-Length, attempt to - # calculate it based on the data object. - if (data and 'Content-Length' not in service.additional_headers and - 'Content-Length' not in extra_headers): - content_length = CalculateDataLength(data) - if content_length: - extra_headers['Content-Length'] = str(content_length) - - if content_type: - extra_headers['Content-Type'] = content_type - - # Send the HTTP headers. - if isinstance(service.additional_headers, dict): - for header in service.additional_headers: - connection.putheader(header, service.additional_headers[header]) - if isinstance(extra_headers, dict): - for header in extra_headers: - connection.putheader(header, extra_headers[header]) - connection.endheaders() - - # If there is data, send it in the request. - if data: - if isinstance(data, list): - for data_part in data: - __SendDataPart(data_part, connection) - else: - __SendDataPart(data, connection) - - # Return the HTTP Response from the server. - return connection.getresponse() - - -def __SendDataPart(data, connection): - """This method is deprecated, use atom.http._send_data_part""" - deprecated('call to deprecated function __SendDataPart') - if isinstance(data, str): - # TODO add handling for unicode. - connection.send(data) - return - elif ElementTree.iselement(data): - connection.send(ElementTree.tostring(data)) - return - # Check to see if data is a file-like object that has a read method. - elif hasattr(data, 'read'): - # Read the file and send it a chunk at a time. - while 1: - binarydata = data.read(100000) - if binarydata == '': break - connection.send(binarydata) - return - else: - # The data object was not a file. - # Try to convert to a string and send the data. - connection.send(str(data)) - return - - -def CalculateDataLength(data): - """Attempts to determine the length of the data to send. - - This method will respond with a length only if the data is a string or - and ElementTree element. - - Args: - data: object If this is not a string or ElementTree element this funtion - will return None. - """ - if isinstance(data, str): - return len(data) - elif isinstance(data, list): - return None - elif ElementTree.iselement(data): - return len(ElementTree.tostring(data)) - elif hasattr(data, 'read'): - # If this is a file-like object, don't try to guess the length. - return None - else: - return len(str(data).encode('utf-8')) - - -def deprecation(message): - warnings.warn(message, DeprecationWarning, stacklevel=2) diff --git a/src/gam/atom/token_store.py b/src/gam/atom/token_store.py deleted file mode 100644 index a7ee46fc..00000000 --- a/src/gam/atom/token_store.py +++ /dev/null @@ -1,105 +0,0 @@ -# -# Copyright (C) 2008 Google Inc. -# -# Licensed under the Apache License 2.0; - - -"""This module provides a TokenStore class which is designed to manage -auth tokens required for different services. - -Each token is valid for a set of scopes which is the start of a URL. An HTTP -client will use a token store to find a valid Authorization header to send -in requests to the specified URL. If the HTTP client determines that a token -has expired or been revoked, it can remove the token from the store so that -it will not be used in future requests. -""" - -# __author__ = 'api.jscudder (Jeff Scudder)' - -import atom.http_interface -import atom.url - -SCOPE_ALL = 'http' - - -class TokenStore(object): - """Manages Authorization tokens which will be sent in HTTP headers.""" - - def __init__(self, scoped_tokens=None): - self._tokens = scoped_tokens or {} - - def add_token(self, token): - """Adds a new token to the store (replaces tokens with the same scope). - - Args: - token: A subclass of http_interface.GenericToken. The token object is - responsible for adding the Authorization header to the HTTP request. - The scopes defined in the token are used to determine if the token - is valid for a requested scope when find_token is called. - - Returns: - True if the token was added, False if the token was not added becase - no scopes were provided. - """ - if not hasattr(token, 'scopes') or not token.scopes: - return False - - for scope in token.scopes: - self._tokens[str(scope)] = token - return True - - def find_token(self, url): - """Selects an Authorization header token which can be used for the URL. - - Args: - url: str or atom.url.Url or a list containing the same. - The URL which is going to be requested. All - tokens are examined to see if any scopes begin match the beginning - of the URL. The first match found is returned. - - Returns: - The token object which should execute the HTTP request. If there was - no token for the url (the url did not begin with any of the token - scopes available), then the atom.http_interface.GenericToken will be - returned because the GenericToken calls through to the http client - without adding an Authorization header. - """ - if url is None: - return None - if isinstance(url, str): - url = atom.url.parse_url(url) - if url in self._tokens: - token = self._tokens[url] - if token.valid_for_scope(url): - return token - else: - del self._tokens[url] - for scope, token in self._tokens.items(): - if token.valid_for_scope(url): - return token - return atom.http_interface.GenericToken() - - def remove_token(self, token): - """Removes the token from the token_store. - - This method is used when a token is determined to be invalid. If the - token was found by find_token, but resulted in a 401 or 403 error stating - that the token was invlid, then the token should be removed to prevent - future use. - - Returns: - True if a token was found and then removed from the token - store. False if the token was not in the TokenStore. - """ - token_found = False - scopes_to_delete = [] - for scope, stored_token in self._tokens.items(): - if stored_token == token: - scopes_to_delete.append(scope) - token_found = True - for scope in scopes_to_delete: - del self._tokens[scope] - return token_found - - def remove_all_tokens(self): - self._tokens = {} diff --git a/src/gam/atom/url.py b/src/gam/atom/url.py deleted file mode 100644 index 3e833ddd..00000000 --- a/src/gam/atom/url.py +++ /dev/null @@ -1,130 +0,0 @@ -# -# Copyright (C) 2008 Google Inc. -# -# Licensed under the Apache License 2.0; - - - -# __author__ = 'api.jscudder (Jeff Scudder)' - -import urllib.error -import urllib.parse -import urllib.parse -import urllib.request - -DEFAULT_PROTOCOL = 'http' -DEFAULT_PORT = 80 - - -def parse_url(url_string): - """Creates a Url object which corresponds to the URL string. - - This method can accept partial URLs, but it will leave missing - members of the Url unset. - """ - parts = urllib.parse.urlparse(url_string) - url = Url() - if parts[0]: - url.protocol = parts[0] - if parts[1]: - host_parts = parts[1].split(':') - if host_parts[0]: - url.host = host_parts[0] - if len(host_parts) > 1: - url.port = host_parts[1] - if parts[2]: - url.path = parts[2] - if parts[4]: - param_pairs = parts[4].split('&') - for pair in param_pairs: - pair_parts = pair.split('=') - if len(pair_parts) > 1: - url.params[urllib.parse.unquote_plus(pair_parts[0])] = ( - urllib.parse.unquote_plus(pair_parts[1])) - elif len(pair_parts) == 1: - url.params[urllib.parse.unquote_plus(pair_parts[0])] = None - return url - - -class Url(object): - """Represents a URL and implements comparison logic. - - URL strings which are not identical can still be equivalent, so this object - provides a better interface for comparing and manipulating URLs than - strings. URL parameters are represented as a dictionary of strings, and - defaults are used for the protocol (http) and port (80) if not provided. - """ - - def __init__(self, protocol=None, host=None, port=None, path=None, - params=None): - self.protocol = protocol - self.host = host - self.port = port - self.path = path - self.params = params or {} - - def to_string(self): - url_parts = ['', '', '', '', '', ''] - if self.protocol: - url_parts[0] = self.protocol - if self.host: - if self.port: - url_parts[1] = ':'.join((self.host, str(self.port))) - else: - url_parts[1] = self.host - if self.path: - url_parts[2] = self.path - if self.params: - url_parts[4] = self.get_param_string() - return urllib.parse.urlunparse(url_parts) - - def get_param_string(self): - param_pairs = [] - for key, value in self.params.items(): - param_pairs.append('='.join((urllib.parse.quote_plus(key), - urllib.parse.quote_plus(str(value))))) - return '&'.join(param_pairs) - - def get_request_uri(self): - """Returns the path with the parameters escaped and appended.""" - param_string = self.get_param_string() - if param_string: - return '?'.join([self.path, param_string]) - else: - return self.path - - def __cmp__(self, other): - if not isinstance(other, Url): - return cmp(self.to_string(), str(other)) - difference = 0 - # Compare the protocol - if self.protocol and other.protocol: - difference = cmp(self.protocol, other.protocol) - elif self.protocol and not other.protocol: - difference = cmp(self.protocol, DEFAULT_PROTOCOL) - elif not self.protocol and other.protocol: - difference = cmp(DEFAULT_PROTOCOL, other.protocol) - if difference != 0: - return difference - # Compare the host - difference = cmp(self.host, other.host) - if difference != 0: - return difference - # Compare the port - if self.port and other.port: - difference = cmp(self.port, other.port) - elif self.port and not other.port: - difference = cmp(self.port, DEFAULT_PORT) - elif not self.port and other.port: - difference = cmp(DEFAULT_PORT, other.port) - if difference != 0: - return difference - # Compare the path - difference = cmp(self.path, other.path) - if difference != 0: - return difference - # Compare the parameters - return cmp(self.params, other.params) - - def __str__(self): - return self.to_string() diff --git a/src/gam/cmd/audit.py b/src/gam/cmd/audit.py index 2886fded..92b59326 100644 --- a/src/gam/cmd/audit.py +++ b/src/gam/cmd/audit.py @@ -1,158 +1,34 @@ -"""GAM audit monitor commands (GDATA). +"""GAM audit monitor commands — REMOVED. -Mailbox monitor creation/deletion/listing and the doWhatIs command. +The Email Audit API has been deprecated by Google. These commands +now print a deprecation notice and exit. """ +from gam.var import Act +from gam.util.args import getChoice +from gam.util.output import systemErrorExit +from gam.constants import CMD_ACTION, CMD_FUNCTION, USAGE_ERROR_RC -from gamlib import state as GM -from gam.var import Act, Cmd, Ent, Ind -from gam.util.access import entityUnknownWarning -from gam.util.api import getEmailAuditObject -from gam.util.api_call import callGData -from gam.util.args import ( - YYYYMMDD_HHMM_FORMAT, - checkForExtraneousArguments, - getArgument, - getEmailAddress, - getString, - getYYYYMMDD_HHMM, - normalizeEmailAddressOrUID, - splitEmailAddress, -) -from gam.util.display import ( - entityActionFailedWarning, - entityActionPerformed, - entityPerformActionNumItems, - printKeyValueList, - printKeyValueListWithCount, -) -from gam.util.errors import invalidArgumentExit, unknownArgumentExit -from gam.util.output import setSysExitRC -from gam.constants import NO_ENTITIES_FOUND_RC +LAST_SUPPORTED_VERSION = '7.46.07' +def _deprecatedCommand(api_name): + systemErrorExit( + USAGE_ERROR_RC, + f'GAM no longer supports the legacy {api_name} API and this command. ' + f'If you must use this API you can install a copy of GAM {LAST_SUPPORTED_VERSION} ' + f'which is the last version to support this command.' + ) -def getAuditParameters(emailAddressRequired=True, requestIdRequired=True, destUserRequired=False): - auditObject = getEmailAuditObject() - emailAddress = getEmailAddress(noUid=True, optional=not emailAddressRequired) - parameters = {} - if emailAddress: - parameters['auditUser'] = emailAddress - parameters['auditUserName'], auditObject.domain = splitEmailAddress(emailAddress) - if requestIdRequired: - parameters['requestId'] = getString(Cmd.OB_REQUEST_ID) - if destUserRequired: - destEmailAddress = getEmailAddress(noUid=True) - parameters['auditDestUser'] = destEmailAddress - parameters['auditDestUserName'], destDomain = splitEmailAddress(destEmailAddress) - if auditObject.domain != destDomain: - Cmd.Backup() - invalidArgumentExit(f'{parameters["auditDestUserName"]}@{auditObject.domain}') - return (auditObject, parameters) - -# Audit monitor command utilities -def _showMailboxMonitorRequestStatus(request, i=0, count=0): - printKeyValueListWithCount(['Destination', normalizeEmailAddressOrUID(request['destUserName'])], i, count) - Ind.Increment() - printKeyValueList(['Begin', request.get('beginDate', 'immediately')]) - printKeyValueList(['End', request['endDate']]) - printKeyValueList(['Monitor Incoming', request['outgoingEmailMonitorLevel']]) - printKeyValueList(['Monitor Outgoing', request['incomingEmailMonitorLevel']]) - printKeyValueList(['Monitor Chats', request.get('chatMonitorLevel', 'NONE')]) - printKeyValueList(['Monitor Drafts', request.get('draftMonitorLevel', 'NONE')]) - Ind.Decrement() - -# gam audit monitor create [begin ] [end ] [incoming_headers] [outgoing_headers] [nochats] [nodrafts] [chat_headers] [draft_headers] def doCreateMonitor(): - auditObject, parameters = getAuditParameters(emailAddressRequired=True, requestIdRequired=False, destUserRequired=True) - #end_date defaults to 30 days in the future... - end_date = GM.Globals[GM.DATETIME_NOW].shift(days=30).strftime(YYYYMMDD_HHMM_FORMAT) - begin_date = None - incoming_headers_only = outgoing_headers_only = drafts_headers_only = chats_headers_only = False - drafts = chats = True - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if myarg == 'begin': - begin_date = getYYYYMMDD_HHMM() - elif myarg == 'end': - end_date = getYYYYMMDD_HHMM() - elif myarg == 'incomingheaders': - incoming_headers_only = True - elif myarg == 'outgoingheaders': - outgoing_headers_only = True - elif myarg == 'nochats': - chats = False - elif myarg == 'nodrafts': - drafts = False - elif myarg == 'chatheaders': - chats_headers_only = True - elif myarg == 'draftheaders': - drafts_headers_only = True - else: - unknownArgumentExit() - try: - request = callGData(auditObject, 'createEmailMonitor', - throwErrors=[GDATA.INVALID_VALUE, GDATA.INVALID_INPUT, GDATA.DOES_NOT_EXIST, GDATA.INVALID_DOMAIN], - source_user=parameters['auditUserName'], destination_user=parameters['auditDestUserName'], end_date=end_date, begin_date=begin_date, - incoming_headers_only=incoming_headers_only, outgoing_headers_only=outgoing_headers_only, - drafts=drafts, drafts_headers_only=drafts_headers_only, chats=chats, chats_headers_only=chats_headers_only) - entityActionPerformed([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None]) - Ind.Increment() - _showMailboxMonitorRequestStatus(request) - Ind.Decrement() - except (GDATA.invalidValue, GDATA.invalidInput) as e: - entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e)) - except (GDATA.doesNotExist, GDATA.invalidDomain) as e: - if str(e).find(parameters['auditUser']) != -1: - entityUnknownWarning(Ent.USER, parameters['auditUser']) - else: - entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e)) + _deprecatedCommand('Email Audit') -# gam audit monitor delete def doDeleteMonitor(): - auditObject, parameters = getAuditParameters(emailAddressRequired=True, requestIdRequired=False, destUserRequired=True) - checkForExtraneousArguments() - try: - callGData(auditObject, 'deleteEmailMonitor', - throwErrors=[GDATA.INVALID_INPUT, GDATA.DOES_NOT_EXIST, GDATA.INVALID_DOMAIN], - source_user=parameters['auditUserName'], destination_user=parameters['auditDestUserName']) - entityActionPerformed([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, parameters['auditDestUser']]) - except GDATA.invalidInput as e: - entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e)) - except (GDATA.doesNotExist, GDATA.invalidDomain) as e: - if str(e).find(parameters['auditUser']) != -1: - entityUnknownWarning(Ent.USER, parameters['auditUser']) - else: - entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e)) + _deprecatedCommand('Email Audit') -# gam audit monitor list def doShowMonitors(): - auditObject, parameters = getAuditParameters(emailAddressRequired=True, requestIdRequired=False, destUserRequired=False) - checkForExtraneousArguments() - try: - results = callGData(auditObject, 'getEmailMonitors', - throwErrors=[GDATA.DOES_NOT_EXIST, GDATA.INVALID_DOMAIN], - user=parameters['auditUserName']) - jcount = len(results) if (results) else 0 - entityPerformActionNumItems([Ent.USER, parameters['auditUser']], jcount, Ent.AUDIT_MONITOR_REQUEST) - if jcount == 0: - setSysExitRC(NO_ENTITIES_FOUND_RC) - return - Ind.Increment() - j = 0 - for request in results: - j += 1 - _showMailboxMonitorRequestStatus(request, j, jcount) - Ind.Decrement() - except (GDATA.doesNotExist, GDATA.invalidDomain): - entityUnknownWarning(Ent.USER, parameters['auditUser']) - -# gam whatis [noinfo] [noinvitablecheck] + _deprecatedCommand('Email Audit') # Dispatch tables and routing (moved from __init__.py) -# Additional imports for dispatch -from gam.util.args import getChoice -from gam.constants import CMD_ACTION, CMD_FUNCTION - AUDIT_SUBCOMMANDS_WITH_OBJECTS = { 'monitor': {'create': (Act.CREATE, doCreateMonitor), @@ -166,4 +42,3 @@ def processAuditCommands(): CL_objectName = getChoice(AUDIT_SUBCOMMANDS_WITH_OBJECTS[CL_subCommand]) Act.Set(AUDIT_SUBCOMMANDS_WITH_OBJECTS[CL_subCommand][CL_objectName][CMD_ACTION]) AUDIT_SUBCOMMANDS_WITH_OBJECTS[CL_subCommand][CL_objectName][CMD_FUNCTION]() - diff --git a/src/gam/cmd/contacts.py b/src/gam/cmd/contacts.py index 885110bb..0da8d743 100644 --- a/src/gam/cmd/contacts.py +++ b/src/gam/cmd/contacts.py @@ -1,56 +1,23 @@ """GAM domain contacts management.""" -import re -import json - -import gdata.apps.contacts - -from gamlib import settings as GC from gamlib import msgs as Msg -from gam.constants import NO_ENTITIES_FOUND_RC - - -from gamlib import gdata as GDATA -from gam.var import Act, Cmd, Ent, Ind -from gam.util.access import entityUnknownWarning -from gam.util.api import getContactsObject, getContactsQuery -from gam.util.api_call import callGData, callGDataPages +from gam.var import Cmd, Ent from gam.util.args import ( - LANGUAGE_CODES_MAP, checkArgumentPresent, - checkForExtraneousArguments, - escapeCRsNLs, - formatLocalTime, getAddCSVData, getArgument, getBoolean, getChoice, - getEmailAddress, getJSON, - getLanguageCode, - getREPattern, getString, getStringWithCRsNLsOrFile, getYYYYMMDD, ) -from gam.util.csv_pf import CSVPrintFile, FormatJSONQuoteChar, _getFieldsList, cleanJSON -from gam.util.display import ( - entityActionFailedWarning, - entityActionPerformed, - entityPerformActionModifierNumItems, - entityPerformActionNumItems, - entityServiceNotApplicableWarning, - getPageMessageForWhom, - printEntity, - printEntityKVList, - printGettingAllEntityItemsForWhom, - printKeyValueList, - printKeyValueWithCRsNLs, - printLine, -) -from gam.util.entity import getEntityList -from gam.util.errors import invalidChoiceExit, missingArgumentExit, unknownArgumentExit, usageErrorExit -from gam.util.output import setSysExitRC, writeStdout +from gam.util.csv_pf import CSVPrintFile +from gam.util.errors import unknownArgumentExit, usageErrorExit +from gam.util.output import systemErrorExit +from gam.constants import USAGE_ERROR_RC + def _getCreateContactReturnOptions(parameters): @@ -66,1521 +33,45 @@ def _getCreateContactReturnOptions(parameters): else: return False return True -# -CONTACT_JSON = 'JSON' -CONTACT_ID = 'ContactID' -CONTACT_UPDATED = 'Updated' -CONTACT_NAME_PREFIX = 'Name Prefix' -CONTACT_GIVEN_NAME = 'Given Name' -CONTACT_ADDITIONAL_NAME = 'Additional Name' -CONTACT_FAMILY_NAME = 'Family Name' -CONTACT_NAME_SUFFIX = 'Name Suffix' -CONTACT_NAME = 'Name' -CONTACT_NICKNAME = 'Nickname' -CONTACT_MAIDENNAME = 'Maiden Name' -CONTACT_SHORTNAME = 'Short Name' -CONTACT_INITIALS = 'Initials' -CONTACT_BIRTHDAY = 'Birthday' -CONTACT_GENDER = 'Gender' -CONTACT_LOCATION = 'Location' -CONTACT_PRIORITY = 'Priority' -CONTACT_SENSITIVITY = 'Sensitivity' -CONTACT_SUBJECT = 'Subject' -CONTACT_LANGUAGE = 'Language' -CONTACT_NOTES = 'Notes' -CONTACT_OCCUPATION = 'Occupation' -CONTACT_BILLING_INFORMATION = 'Billing Information' -CONTACT_MILEAGE = 'Mileage' -CONTACT_DIRECTORY_SERVER = 'Directory Server' -CONTACT_ADDRESSES = 'Addresses' -CONTACT_CALENDARS = 'Calendars' -CONTACT_EMAILS = 'Emails' -CONTACT_EXTERNALIDS = 'External IDs' -CONTACT_EVENTS = 'Events' -CONTACT_HOBBIES = 'Hobbies' -CONTACT_IMS = 'IMs' -CONTACT_JOTS = 'Jots' -CONTACT_ORGANIZATIONS = 'Organizations' -CONTACT_PHONES = 'Phones' -CONTACT_RELATIONS = 'Relations' -CONTACT_USER_DEFINED_FIELDS = 'User Defined Fields' -CONTACT_WEBSITES = 'Websites' -# -class ContactsManager: - CONTACT_ARGUMENT_TO_PROPERTY_MAP = { - 'json': CONTACT_JSON, - 'name': CONTACT_NAME, - 'prefix': CONTACT_NAME_PREFIX, - 'givenname': CONTACT_GIVEN_NAME, - 'additionalname': CONTACT_ADDITIONAL_NAME, - 'familyname': CONTACT_FAMILY_NAME, - 'firstname': CONTACT_GIVEN_NAME, - 'middlename': CONTACT_ADDITIONAL_NAME, - 'lastname': CONTACT_FAMILY_NAME, - 'suffix': CONTACT_NAME_SUFFIX, - 'nickname': CONTACT_NICKNAME, - 'maidenname': CONTACT_MAIDENNAME, - 'shortname': CONTACT_SHORTNAME, - 'initials': CONTACT_INITIALS, - 'birthday': CONTACT_BIRTHDAY, - 'gender': CONTACT_GENDER, - 'location': CONTACT_LOCATION, - 'priority': CONTACT_PRIORITY, - 'sensitivity': CONTACT_SENSITIVITY, - 'subject': CONTACT_SUBJECT, - 'language': CONTACT_LANGUAGE, - 'note': CONTACT_NOTES, - 'notes': CONTACT_NOTES, - 'occupation': CONTACT_OCCUPATION, - 'billinginfo': CONTACT_BILLING_INFORMATION, - 'mileage': CONTACT_MILEAGE, - 'directoryserver': CONTACT_DIRECTORY_SERVER, - 'address': CONTACT_ADDRESSES, - 'addresses': CONTACT_ADDRESSES, - 'calendar': CONTACT_CALENDARS, - 'calendars': CONTACT_CALENDARS, - 'email': CONTACT_EMAILS, - 'emails': CONTACT_EMAILS, - 'externalid': CONTACT_EXTERNALIDS, - 'externalids': CONTACT_EXTERNALIDS, - 'event': CONTACT_EVENTS, - 'events': CONTACT_EVENTS, - 'hobby': CONTACT_HOBBIES, - 'hobbies': CONTACT_HOBBIES, - 'im': CONTACT_IMS, - 'ims': CONTACT_IMS, - 'jot': CONTACT_JOTS, - 'jots': CONTACT_JOTS, - 'organization': CONTACT_ORGANIZATIONS, - 'organizations': CONTACT_ORGANIZATIONS, - 'organisation': CONTACT_ORGANIZATIONS, - 'organisations': CONTACT_ORGANIZATIONS, - 'phone': CONTACT_PHONES, - 'phones': CONTACT_PHONES, - 'relation': CONTACT_RELATIONS, - 'relations': CONTACT_RELATIONS, - 'userdefinedfield': CONTACT_USER_DEFINED_FIELDS, - 'userdefinedfields': CONTACT_USER_DEFINED_FIELDS, - 'website': CONTACT_WEBSITES, - 'websites': CONTACT_WEBSITES, - 'updated': CONTACT_UPDATED, - } +# Domain Shared Contacts — REMOVED (GData Contacts API v3 deprecated) +LAST_SUPPORTED_VERSION = '7.46.07' - GENDER_CHOICE_MAP = {'male': 'male', 'female': 'female'} +def _deprecatedDomainContactCommand(): + systemErrorExit( + USAGE_ERROR_RC, + f'GAM no longer supports the legacy Domain Shared Contacts (GData Contacts v3) API ' + f'and this command. If you must use this API you can install a copy of GAM ' + f'{LAST_SUPPORTED_VERSION} which is the last version to support this command.' + ) - PRIORITY_CHOICE_MAP = {'low': 'low', 'normal': 'normal', 'high': 'high'} +def doCreateDomainContact(): + _deprecatedDomainContactCommand() - SENSITIVITY_CHOICE_MAP = { - 'confidential': 'confidential', - 'normal': 'normal', - 'personal': 'personal', - 'private': 'private', - } +def doClearDomainContacts(): + _deprecatedDomainContactCommand() - CONTACT_NAME_FIELDS = ( - CONTACT_NAME_PREFIX, - CONTACT_GIVEN_NAME, - CONTACT_ADDITIONAL_NAME, - CONTACT_FAMILY_NAME, - CONTACT_NAME_SUFFIX, - ) +def doUpdateDomainContacts(): + _deprecatedDomainContactCommand() - ADDRESS_TYPE_ARGUMENT_TO_REL = { - 'work': gdata.apps.contacts.REL_WORK, - 'home': gdata.apps.contacts.REL_HOME, - 'other': gdata.apps.contacts.REL_OTHER, - } +def doDedupDomainContacts(): + _deprecatedDomainContactCommand() - ADDRESS_REL_TO_TYPE_ARGUMENT = { - gdata.apps.contacts.REL_WORK: 'work', - gdata.apps.contacts.REL_HOME: 'home', - gdata.apps.contacts.REL_OTHER: 'other', - } +def doDeleteDomainContacts(): + _deprecatedDomainContactCommand() - ADDRESS_ARGUMENT_TO_FIELD_MAP = { - 'streetaddress': 'street', - 'pobox': 'pobox', - 'neighborhood': 'neighborhood', - 'locality': 'city', - 'region': 'region', - 'postalcode': 'postcode', - 'country': 'country', - 'formatted': 'value', 'unstructured': 'value', - } +def doInfoDomainContacts(): + _deprecatedDomainContactCommand() - ADDRESS_FIELD_TO_ARGUMENT_MAP = { - 'street': 'streetaddress', - 'pobox': 'pobox', - 'neighborhood': 'neighborhood', - 'city': 'locality', - 'region': 'region', - 'postcode': 'postalcode', - 'country': 'country', - } +def doPrintShowDomainContacts(): + _deprecatedDomainContactCommand() - ADDRESS_FIELD_PRINT_ORDER = [ - 'street', - 'pobox', - 'neighborhood', - 'city', - 'region', - 'postcode', - 'country', - ] - - CALENDAR_TYPE_ARGUMENT_TO_REL = { - 'work': 'work', - 'home': 'home', - 'free-busy': 'free-busy', - } - - CALENDAR_REL_TO_TYPE_ARGUMENT = { - 'work': 'work', - 'home': 'home', - 'free-busy': 'free-busy', - } - - EMAIL_TYPE_ARGUMENT_TO_REL = { - 'work': gdata.apps.contacts.REL_WORK, - 'home': gdata.apps.contacts.REL_HOME, - 'other': gdata.apps.contacts.REL_OTHER, - } - - EMAIL_REL_TO_TYPE_ARGUMENT = { - gdata.apps.contacts.REL_WORK: 'work', - gdata.apps.contacts.REL_HOME: 'home', - gdata.apps.contacts.REL_OTHER: 'other', - } - - EVENT_TYPE_ARGUMENT_TO_REL = { - 'anniversary': 'anniversary', - 'other': 'other', - } - - EVENT_REL_TO_TYPE_ARGUMENT = { - 'anniversary': 'anniversary', - 'other': 'other', - } - - EXTERNALID_TYPE_ARGUMENT_TO_REL = { - 'account': 'account', - 'customer': 'customer', - 'network': 'network', - 'organization': 'organization', - 'organisation': 'organization', - } - - EXTERNALID_REL_TO_TYPE_ARGUMENT = { - 'account': 'account', - 'customer': 'customer', - 'network': 'network', - 'organization': 'organization', - 'organisation': 'organization', - } - - IM_TYPE_ARGUMENT_TO_REL = { - 'work': gdata.apps.contacts.REL_WORK, - 'home': gdata.apps.contacts.REL_HOME, - 'other': gdata.apps.contacts.REL_OTHER, - } - - IM_REL_TO_TYPE_ARGUMENT = { - gdata.apps.contacts.REL_WORK: 'work', - gdata.apps.contacts.REL_HOME: 'home', - gdata.apps.contacts.REL_OTHER: 'other', - } - - IM_PROTOCOL_TO_REL_MAP = { - 'aim': gdata.apps.contacts.IM_AIM, - 'gtalk': gdata.apps.contacts.IM_GOOGLE_TALK, - 'icq': gdata.apps.contacts.IM_ICQ, - 'jabber': gdata.apps.contacts.IM_JABBER, - 'msn': gdata.apps.contacts.IM_MSN, - 'netmeeting': gdata.apps.contacts.IM_NETMEETING, - 'qq': gdata.apps.contacts.IM_QQ, - 'skype': gdata.apps.contacts.IM_SKYPE, - 'xmpp': gdata.apps.contacts.IM_JABBER, - 'yahoo': gdata.apps.contacts.IM_YAHOO, - } - - IM_REL_TO_PROTOCOL_MAP = { - gdata.apps.contacts.IM_AIM: 'aim', - gdata.apps.contacts.IM_GOOGLE_TALK: 'gtalk', - gdata.apps.contacts.IM_ICQ: 'icq', - gdata.apps.contacts.IM_JABBER: 'jabber', - gdata.apps.contacts.IM_MSN: 'msn', - gdata.apps.contacts.IM_NETMEETING: 'netmeeting', - gdata.apps.contacts.IM_QQ: 'qq', - gdata.apps.contacts.IM_SKYPE: 'skype', - gdata.apps.contacts.IM_YAHOO: 'yahoo', - } - - JOT_TYPE_ARGUMENT_TO_REL = { - 'work': 'work', - 'home': 'home', - 'other': 'other', - 'keywords': 'keywords', - 'user': 'user', - } - - JOT_REL_TO_TYPE_ARGUMENT = { - 'work': 'work', - 'home': 'home', - 'other': 'other', - 'keywords': 'keywords', - 'user': 'user', - } - - ORGANIZATION_TYPE_ARGUMENT_TO_REL = { - 'work': gdata.apps.contacts.REL_WORK, - 'other': gdata.apps.contacts.REL_OTHER, - } - - ORGANIZATION_REL_TO_TYPE_ARGUMENT = { - gdata.apps.contacts.REL_WORK: 'work', - gdata.apps.contacts.REL_OTHER: 'other', - } - - ORGANIZATION_ARGUMENT_TO_FIELD_MAP = { - 'location': 'where', - 'department': 'department', - 'title': 'title', - 'jobdescription': 'jobdescription', - 'symbol': 'symbol', - } - - ORGANIZATION_FIELD_TO_ARGUMENT_MAP = { - 'where': 'location', - 'department': 'department', - 'title': 'title', - 'jobdescription': 'jobdescription', - 'symbol': 'symbol', - } - - ORGANIZATION_FIELD_PRINT_ORDER = [ - 'where', - 'department', - 'title', - 'jobdescription', - 'symbol', - ] - - PHONE_TYPE_ARGUMENT_TO_REL = { - 'work': gdata.apps.contacts.PHONE_WORK, - 'home': gdata.apps.contacts.PHONE_HOME, - 'other': gdata.apps.contacts.PHONE_OTHER, - 'fax': gdata.apps.contacts.PHONE_FAX, - 'home_fax': gdata.apps.contacts.PHONE_HOME_FAX, - 'work_fax': gdata.apps.contacts.PHONE_WORK_FAX, - 'other_fax': gdata.apps.contacts.PHONE_OTHER_FAX, - 'main': gdata.apps.contacts.PHONE_MAIN, - 'company_main': gdata.apps.contacts.PHONE_COMPANY_MAIN, - 'assistant': gdata.apps.contacts.PHONE_ASSISTANT, - 'mobile': gdata.apps.contacts.PHONE_MOBILE, - 'work_mobile': gdata.apps.contacts.PHONE_WORK_MOBILE, - 'pager': gdata.apps.contacts.PHONE_PAGER, - 'work_pager': gdata.apps.contacts.PHONE_WORK_PAGER, - 'car': gdata.apps.contacts.PHONE_CAR, - 'radio': gdata.apps.contacts.PHONE_RADIO, - 'callback': gdata.apps.contacts.PHONE_CALLBACK, - 'isdn': gdata.apps.contacts.PHONE_ISDN, - 'telex': gdata.apps.contacts.PHONE_TELEX, - 'tty_tdd': gdata.apps.contacts.PHONE_TTY_TDD, - } - - PHONE_REL_TO_TYPE_ARGUMENT = { - gdata.apps.contacts.PHONE_WORK: 'work', - gdata.apps.contacts.PHONE_HOME: 'home', - gdata.apps.contacts.PHONE_OTHER: 'other', - gdata.apps.contacts.PHONE_FAX: 'fax', - gdata.apps.contacts.PHONE_HOME_FAX: 'home_fax', - gdata.apps.contacts.PHONE_WORK_FAX: 'work_fax', - gdata.apps.contacts.PHONE_OTHER_FAX: 'other_fax', - gdata.apps.contacts.PHONE_MAIN: 'main', - gdata.apps.contacts.PHONE_COMPANY_MAIN: 'company_main', - gdata.apps.contacts.PHONE_ASSISTANT: 'assistant', - gdata.apps.contacts.PHONE_MOBILE: 'mobile', - gdata.apps.contacts.PHONE_WORK_MOBILE: 'work_mobile', - gdata.apps.contacts.PHONE_PAGER: 'pager', - gdata.apps.contacts.PHONE_WORK_PAGER: 'work_pager', - gdata.apps.contacts.PHONE_CAR: 'car', - gdata.apps.contacts.PHONE_RADIO: 'radio', - gdata.apps.contacts.PHONE_CALLBACK: 'callback', - gdata.apps.contacts.PHONE_ISDN: 'isdn', - gdata.apps.contacts.PHONE_TELEX: 'telex', - gdata.apps.contacts.PHONE_TTY_TDD: 'tty_tdd', - } - - RELATION_TYPE_ARGUMENT_TO_REL = { - 'spouse': 'spouse', - 'child': 'child', - 'mother': 'mother', - 'father': 'father', - 'parent': 'parent', - 'brother': 'brother', - 'sister': 'sister', - 'friend': 'friend', - 'relative': 'relative', - 'manager': 'manager', - 'assistant': 'assistant', - 'referredby': 'referred-by', - 'partner': 'partner', - 'domesticpartner': 'domestic-partner', - } - - RELATION_REL_TO_TYPE_ARGUMENT = { - 'spouse' : 'spouse', - 'child' : 'child', - 'mother' : 'mother', - 'father' : 'father', - 'parent' : 'parent', - 'brother' : 'brother', - 'sister' : 'sister', - 'friend' : 'friend', - 'relative' : 'relative', - 'manager' : 'manager', - 'assistant' : 'assistant', - 'referred-by' : 'referred_by', - 'partner' : 'partner', - 'domestic-partner' : 'domestic_partner', - } - - WEBSITE_TYPE_ARGUMENT_TO_REL = { - 'home-page': 'home-page', - 'blog': 'blog', - 'profile': 'profile', - 'work': 'work', - 'home': 'home', - 'other': 'other', - 'ftp': 'ftp', - 'reservations': 'reservations', - 'app-install-page': 'app-install-page', - } - - WEBSITE_REL_TO_TYPE_ARGUMENT = { - 'home-page': 'home-page', - 'blog': 'blog', - 'profile': 'profile', - 'work': 'work', - 'home': 'home', - 'other': 'other', - 'ftp': 'ftp', - 'reservations': 'reservations', - 'app-install-page': 'app-install-page', - } - - CONTACT_NAME_PROPERTY_PRINT_ORDER = [ - CONTACT_UPDATED, - CONTACT_NAME, - CONTACT_NAME_PREFIX, - CONTACT_GIVEN_NAME, - CONTACT_ADDITIONAL_NAME, - CONTACT_FAMILY_NAME, - CONTACT_NAME_SUFFIX, - CONTACT_NICKNAME, - CONTACT_MAIDENNAME, - CONTACT_SHORTNAME, - CONTACT_INITIALS, - CONTACT_BIRTHDAY, - CONTACT_GENDER, - CONTACT_LOCATION, - CONTACT_PRIORITY, - CONTACT_SENSITIVITY, - CONTACT_SUBJECT, - CONTACT_LANGUAGE, - CONTACT_NOTES, - CONTACT_OCCUPATION, - CONTACT_BILLING_INFORMATION, - CONTACT_MILEAGE, - CONTACT_DIRECTORY_SERVER, - ] - - CONTACT_ARRAY_PROPERTY_PRINT_ORDER = [ - CONTACT_ADDRESSES, - CONTACT_EMAILS, - CONTACT_IMS, - CONTACT_PHONES, - CONTACT_CALENDARS, - CONTACT_ORGANIZATIONS, - CONTACT_EXTERNALIDS, - CONTACT_EVENTS, - CONTACT_HOBBIES, - CONTACT_JOTS, - CONTACT_RELATIONS, - CONTACT_WEBSITES, - CONTACT_USER_DEFINED_FIELDS, - ] - - CONTACT_ARRAY_PROPERTIES = { - CONTACT_ADDRESSES: {'relMap': ADDRESS_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'formatted', 'primary': True}, - CONTACT_EMAILS: {'relMap': EMAIL_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'address', 'primary': True}, - CONTACT_IMS: {'relMap': IM_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'address', 'primary': True}, - CONTACT_PHONES: {'relMap': PHONE_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'value', 'primary': True}, - CONTACT_CALENDARS: {'relMap': CALENDAR_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'address', 'primary': True}, - CONTACT_ORGANIZATIONS: {'relMap': ORGANIZATION_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'name', 'primary': True}, - CONTACT_EXTERNALIDS: {'relMap': EXTERNALID_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'value', 'primary': False}, - CONTACT_EVENTS: {'relMap': EVENT_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'date', 'primary': False}, - CONTACT_HOBBIES: {'relMap': None, 'infoTitle': 'value', 'primary': False}, - CONTACT_JOTS: {'relMap': JOT_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'value', 'primary': False}, - CONTACT_RELATIONS: {'relMap': RELATION_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'value', 'primary': False}, - CONTACT_USER_DEFINED_FIELDS: {'relMap': None, 'infoTitle': 'value', 'primary': False}, - CONTACT_WEBSITES: {'relMap': WEBSITE_REL_TO_TYPE_ARGUMENT, 'infoTitle': 'value', 'primary': True}, - } - - @staticmethod - def GetContactShortId(contactEntry): - full_id = contactEntry.id.text - return full_id[full_id.rfind('/')+1:] - - @staticmethod - def GetContactFields(parameters=None): - - fields = {} - - def CheckClearFieldsList(fieldName): - if checkArgumentPresent(Cmd.CLEAR_NONE_ARGUMENT): - fields.pop(fieldName, None) - fields[fieldName] = [] - return True - return False - - def InitArrayItem(choices): - item = {} - rel = getChoice(choices, mapChoice=True, defaultChoice=None) - if rel: - item['rel'] = rel - item['label'] = None - else: - item['rel'] = None - item['label'] = getString(Cmd.OB_STRING) - return item - - def PrimaryNotPrimary(pnp, entry): - if pnp == 'notprimary': - entry['primary'] = 'false' - return True - if pnp == 'primary': - entry['primary'] = 'true' - primary['location'] = Cmd.Location() - return True - return False - - def GetPrimaryNotPrimaryChoice(entry): - if not getChoice({'primary': True, 'notprimary': False}, mapChoice=True): - entry['primary'] = 'false' - else: - entry['primary'] = 'true' - primary['location'] = Cmd.Location() - - def AppendItemToFieldsList(fieldName, fieldValue, checkBlankField=None): - fields.setdefault(fieldName, []) - if checkBlankField is None or fieldValue[checkBlankField]: - if isinstance(fieldValue, dict) and fieldValue.get('primary', 'false') == 'true': - for citem in fields[fieldName]: - if citem.get('primary', 'false') == 'true': - Cmd.SetLocation(primary['location']-1) - usageErrorExit(Msg.MULTIPLE_ITEMS_MARKED_PRIMARY.format(fieldName)) - fields[fieldName].append(fieldValue) - - primary = {} - while Cmd.ArgumentsRemaining(): - if parameters is not None: - if _getCreateContactReturnOptions(parameters): - continue - Cmd.Backup() - fieldName = getChoice(ContactsManager.CONTACT_ARGUMENT_TO_PROPERTY_MAP, mapChoice=True) - if fieldName == CONTACT_BIRTHDAY: - fields[fieldName] = getYYYYMMDD(minLen=0) - elif fieldName == CONTACT_GENDER: - fields[fieldName] = getChoice(ContactsManager.GENDER_CHOICE_MAP, mapChoice=True) - elif fieldName == CONTACT_PRIORITY: - fields[fieldName] = getChoice(ContactsManager.PRIORITY_CHOICE_MAP, mapChoice=True) - elif fieldName == CONTACT_SENSITIVITY: - fields[fieldName] = getChoice(ContactsManager.SENSITIVITY_CHOICE_MAP, mapChoice=True) - elif fieldName == CONTACT_LANGUAGE: - fields[fieldName] = getLanguageCode(LANGUAGE_CODES_MAP) - elif fieldName == CONTACT_NOTES: - fields[fieldName] = getStringWithCRsNLsOrFile()[0] - elif fieldName == CONTACT_ADDRESSES: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.ADDRESS_TYPE_ARGUMENT_TO_REL) - entry['primary'] = 'false' - while Cmd.ArgumentsRemaining(): - argument = getArgument() - if argument in ContactsManager.ADDRESS_ARGUMENT_TO_FIELD_MAP: - value = getString(Cmd.OB_STRING, minLen=0) - if value: - entry[ContactsManager.ADDRESS_ARGUMENT_TO_FIELD_MAP[argument]] = value.replace('\\n', '\n') - elif PrimaryNotPrimary(argument, entry): - break - else: - unknownArgumentExit() - AppendItemToFieldsList(fieldName, entry) - elif fieldName == CONTACT_CALENDARS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.CALENDAR_TYPE_ARGUMENT_TO_REL) - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - GetPrimaryNotPrimaryChoice(entry) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_EMAILS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.EMAIL_TYPE_ARGUMENT_TO_REL) - entry['value'] = getEmailAddress(noUid=True, minLen=0) - GetPrimaryNotPrimaryChoice(entry) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_EVENTS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.EVENT_TYPE_ARGUMENT_TO_REL) - entry['value'] = getYYYYMMDD(minLen=0) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_EXTERNALIDS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.EXTERNALID_TYPE_ARGUMENT_TO_REL) - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_HOBBIES: - if CheckClearFieldsList(fieldName): - continue - entry = {'value': getString(Cmd.OB_STRING, minLen=0)} - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_IMS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.IM_TYPE_ARGUMENT_TO_REL) - entry['protocol'] = getChoice(ContactsManager.IM_PROTOCOL_TO_REL_MAP, mapChoice=True) - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - GetPrimaryNotPrimaryChoice(entry) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_JOTS: - if CheckClearFieldsList(fieldName): - continue - entry = {'rel': getChoice(ContactsManager.JOT_TYPE_ARGUMENT_TO_REL, mapChoice=True)} - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_ORGANIZATIONS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.ORGANIZATION_TYPE_ARGUMENT_TO_REL) - entry['primary'] = 'false' - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - while Cmd.ArgumentsRemaining(): - argument = getArgument() - if argument in ContactsManager.ORGANIZATION_ARGUMENT_TO_FIELD_MAP: - value = getString(Cmd.OB_STRING, minLen=0) - if value: - entry[ContactsManager.ORGANIZATION_ARGUMENT_TO_FIELD_MAP[argument]] = value - elif PrimaryNotPrimary(argument, entry): - break - else: - unknownArgumentExit() - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_PHONES: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.PHONE_TYPE_ARGUMENT_TO_REL) - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - GetPrimaryNotPrimaryChoice(entry) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_RELATIONS: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.RELATION_TYPE_ARGUMENT_TO_REL) - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_USER_DEFINED_FIELDS: - if CheckClearFieldsList(fieldName): - continue - entry = {'rel': getString(Cmd.OB_STRING, minLen=0), 'value': getString(Cmd.OB_STRING, minLen=0)} - if not entry['rel'] or entry['rel'].lower() == 'none': - entry['rel'] = None - AppendItemToFieldsList(fieldName, entry, 'value') - elif fieldName == CONTACT_WEBSITES: - if CheckClearFieldsList(fieldName): - continue - entry = InitArrayItem(ContactsManager.WEBSITE_TYPE_ARGUMENT_TO_REL) - entry['value'] = getString(Cmd.OB_STRING, minLen=0) - GetPrimaryNotPrimaryChoice(entry) - AppendItemToFieldsList(fieldName, entry, 'value') - else: - fields[fieldName] = getString(Cmd.OB_STRING, minLen=0) - return fields - - @staticmethod - def FieldsToContact(fields): - def GetField(fieldName): - return fields.get(fieldName) - - def SetClassAttribute(value, fieldClass, processNLs, attr): - if value: - if processNLs: - value = value.replace('\\n', '\n') - if attr == 'text': - return fieldClass(text=value) - if attr == 'code': - return fieldClass(code=value) - if attr == 'rel': - return fieldClass(rel=value) - if attr == 'value': - return fieldClass(value=value) - if attr == 'value_string': - return fieldClass(value_string=value) - if attr == 'when': - return fieldClass(when=value) - return None - - def GetContactField(fieldName, fieldClass, processNLs=False, attr='text'): - return SetClassAttribute(fields.get(fieldName), fieldClass, processNLs, attr) - - def GetListEntryField(entry, fieldName, fieldClass, processNLs=False, attr='text'): - return SetClassAttribute(entry.get(fieldName), fieldClass, processNLs, attr) - - contactEntry = gdata.apps.contacts.ContactEntry() - value = GetField(CONTACT_NAME) - if not value: - value = ' '.join([fields[fieldName] for fieldName in ContactsManager.CONTACT_NAME_FIELDS if fieldName in fields]) - contactEntry.name = gdata.apps.contacts.Name(full_name=gdata.apps.contacts.FullName(text=value)) - contactEntry.name.name_prefix = GetContactField(CONTACT_NAME_PREFIX, gdata.apps.contacts.NamePrefix) - contactEntry.name.given_name = GetContactField(CONTACT_GIVEN_NAME, gdata.apps.contacts.GivenName) - contactEntry.name.additional_name = GetContactField(CONTACT_ADDITIONAL_NAME, gdata.apps.contacts.AdditionalName) - contactEntry.name.family_name = GetContactField(CONTACT_FAMILY_NAME, gdata.apps.contacts.FamilyName) - contactEntry.name.name_suffix = GetContactField(CONTACT_NAME_SUFFIX, gdata.apps.contacts.NameSuffix) - contactEntry.nickname = GetContactField(CONTACT_NICKNAME, gdata.apps.contacts.Nickname) - contactEntry.maidenName = GetContactField(CONTACT_MAIDENNAME, gdata.apps.contacts.MaidenName) - contactEntry.shortName = GetContactField(CONTACT_SHORTNAME, gdata.apps.contacts.ShortName) - contactEntry.initials = GetContactField(CONTACT_INITIALS, gdata.apps.contacts.Initials) - contactEntry.birthday = GetContactField(CONTACT_BIRTHDAY, gdata.apps.contacts.Birthday, attr='when') - contactEntry.gender = GetContactField(CONTACT_GENDER, gdata.apps.contacts.Gender, attr='value') - contactEntry.where = GetContactField(CONTACT_LOCATION, gdata.apps.contacts.Where, attr='value_string') - contactEntry.priority = GetContactField(CONTACT_PRIORITY, gdata.apps.contacts.Priority, attr='rel') - contactEntry.sensitivity = GetContactField(CONTACT_SENSITIVITY, gdata.apps.contacts.Sensitivity, attr='rel') - contactEntry.subject = GetContactField(CONTACT_SUBJECT, gdata.apps.contacts.Subject) - contactEntry.language = GetContactField(CONTACT_LANGUAGE, gdata.apps.contacts.Language, attr='code') - contactEntry.content = GetContactField(CONTACT_NOTES, gdata.apps.contacts.Content, processNLs=True) - contactEntry.occupation = GetContactField(CONTACT_OCCUPATION, gdata.apps.contacts.Occupation) - contactEntry.billingInformation = GetContactField(CONTACT_BILLING_INFORMATION, gdata.apps.contacts.BillingInformation, processNLs=True) - contactEntry.mileage = GetContactField(CONTACT_MILEAGE, gdata.apps.contacts.Mileage) - contactEntry.directoryServer = GetContactField(CONTACT_DIRECTORY_SERVER, gdata.apps.contacts.DirectoryServer) - value = GetField(CONTACT_ADDRESSES) - if value: - for address in value: - street = GetListEntryField(address, 'street', gdata.apps.contacts.Street) - pobox = GetListEntryField(address, 'pobox', gdata.apps.contacts.PoBox) - neighborhood = GetListEntryField(address, 'neighborhood', gdata.apps.contacts.Neighborhood) - city = GetListEntryField(address, 'city', gdata.apps.contacts.City) - region = GetListEntryField(address, 'region', gdata.apps.contacts.Region) - postcode = GetListEntryField(address, 'postcode', gdata.apps.contacts.Postcode) - country = GetListEntryField(address, 'country', gdata.apps.contacts.Country) - formatted_address = GetListEntryField(address, 'value', gdata.apps.contacts.FormattedAddress, processNLs=True) - contactEntry.structuredPostalAddress.append(gdata.apps.contacts.StructuredPostalAddress(street=street, pobox=pobox, neighborhood=neighborhood, - city=city, region=region, - postcode=postcode, country=country, - formatted_address=formatted_address, - rel=address['rel'], label=address['label'], primary=address['primary'])) - value = GetField(CONTACT_CALENDARS) - if value: - for calendarLink in value: - contactEntry.calendarLink.append(gdata.apps.contacts.CalendarLink(href=calendarLink['value'], rel=calendarLink['rel'], label=calendarLink['label'], primary=calendarLink['primary'])) - value = GetField(CONTACT_EMAILS) - if value: - for emailaddr in value: - contactEntry.email.append(gdata.apps.contacts.Email(address=emailaddr['value'], rel=emailaddr['rel'], label=emailaddr['label'], primary=emailaddr['primary'])) - value = GetField(CONTACT_EXTERNALIDS) - if value: - for externalid in value: - contactEntry.externalId.append(gdata.apps.contacts.ExternalId(value=externalid['value'], rel=externalid['rel'], label=externalid['label'])) - value = GetField(CONTACT_EVENTS) - if value: - for event in value: - contactEntry.event.append(gdata.apps.contacts.Event(rel=event['rel'], label=event['label'], - when=gdata.apps.contacts.When(startTime=event['value']))) - value = GetField(CONTACT_HOBBIES) - if value: - for hobby in value: - contactEntry.hobby.append(gdata.apps.contacts.Hobby(text=hobby['value'])) - value = GetField(CONTACT_IMS) - if value: - for im in value: - contactEntry.im.append(gdata.apps.contacts.IM(address=im['value'], protocol=im['protocol'], rel=im['rel'], label=im['label'], primary=im['primary'])) - value = GetField(CONTACT_JOTS) - if value: - for jot in value: - contactEntry.jot.append(gdata.apps.contacts.Jot(text=jot['value'], rel=jot['rel'])) - value = GetField(CONTACT_ORGANIZATIONS) - if value: - for organization in value: - org_name = gdata.apps.contacts.OrgName(text=organization['value']) - department = GetListEntryField(organization, 'department', gdata.apps.contacts.OrgDepartment) - title = GetListEntryField(organization, 'title', gdata.apps.contacts.OrgTitle) - job_description = GetListEntryField(organization, 'jobdescription', gdata.apps.contacts.OrgJobDescription) - symbol = GetListEntryField(organization, 'symbol', gdata.apps.contacts.OrgSymbol) - where = GetListEntryField(organization, 'where', gdata.apps.contacts.Where, attr='value_string') - contactEntry.organization.append(gdata.apps.contacts.Organization(name=org_name, department=department, - title=title, job_description=job_description, - symbol=symbol, where=where, - rel=organization['rel'], label=organization['label'], primary=organization['primary'])) - value = GetField(CONTACT_PHONES) - if value: - for phone in value: - contactEntry.phoneNumber.append(gdata.apps.contacts.PhoneNumber(text=phone['value'], rel=phone['rel'], label=phone['label'], primary=phone['primary'])) - value = GetField(CONTACT_RELATIONS) - if value: - for relation in value: - contactEntry.relation.append(gdata.apps.contacts.Relation(text=relation['value'], rel=relation['rel'], label=relation['label'])) - value = GetField(CONTACT_USER_DEFINED_FIELDS) - if value: - for userdefinedfield in value: - contactEntry.userDefinedField.append(gdata.apps.contacts.UserDefinedField(key=userdefinedfield['rel'], value=userdefinedfield['value'])) - value = GetField(CONTACT_WEBSITES) - if value: - for website in value: - contactEntry.website.append(gdata.apps.contacts.Website(href=website['value'], rel=website['rel'], label=website['label'], primary=website['primary'])) - return contactEntry - - @staticmethod - def ContactToFields(contactEntry): - fields = {} - def GetContactField(fieldName, attrlist): - objAttr = contactEntry - for attr in attrlist: - objAttr = getattr(objAttr, attr) - if not objAttr: - return - fields[fieldName] = objAttr - - def GetListEntryField(entry, attrlist): - objAttr = entry - for attr in attrlist: - objAttr = getattr(objAttr, attr) - if not objAttr: - return None - return objAttr - - def AppendItemToFieldsList(fieldName, fieldValue): - fields.setdefault(fieldName, []) - fields[fieldName].append(fieldValue) - - fields[CONTACT_ID] = ContactsManager.GetContactShortId(contactEntry) - GetContactField(CONTACT_UPDATED, ['updated', 'text']) - if not contactEntry.deleted: - GetContactField(CONTACT_NAME, ['title', 'text']) - else: - fields[CONTACT_NAME] = 'Deleted' - GetContactField(CONTACT_NAME_PREFIX, ['name', 'name_prefix', 'text']) - GetContactField(CONTACT_GIVEN_NAME, ['name', 'given_name', 'text']) - GetContactField(CONTACT_ADDITIONAL_NAME, ['name', 'additional_name', 'text']) - GetContactField(CONTACT_FAMILY_NAME, ['name', 'family_name', 'text']) - GetContactField(CONTACT_NAME_SUFFIX, ['name', 'name_suffix', 'text']) - GetContactField(CONTACT_NICKNAME, ['nickname', 'text']) - GetContactField(CONTACT_MAIDENNAME, ['maidenName', 'text']) - GetContactField(CONTACT_SHORTNAME, ['shortName', 'text']) - GetContactField(CONTACT_INITIALS, ['initials', 'text']) - GetContactField(CONTACT_BIRTHDAY, ['birthday', 'when']) - GetContactField(CONTACT_GENDER, ['gender', 'value']) - GetContactField(CONTACT_SUBJECT, ['subject', 'text']) - GetContactField(CONTACT_LANGUAGE, ['language', 'code']) - GetContactField(CONTACT_PRIORITY, ['priority', 'rel']) - GetContactField(CONTACT_SENSITIVITY, ['sensitivity', 'rel']) - GetContactField(CONTACT_NOTES, ['content', 'text']) - GetContactField(CONTACT_LOCATION, ['where', 'value_string']) - GetContactField(CONTACT_OCCUPATION, ['occupation', 'text']) - GetContactField(CONTACT_BILLING_INFORMATION, ['billingInformation', 'text']) - GetContactField(CONTACT_MILEAGE, ['mileage', 'text']) - GetContactField(CONTACT_DIRECTORY_SERVER, ['directoryServer', 'text']) - for address in contactEntry.structuredPostalAddress: - AppendItemToFieldsList(CONTACT_ADDRESSES, - {'rel': address.rel, - 'label': address.label, - 'value': GetListEntryField(address, ['formatted_address', 'text']), - 'street': GetListEntryField(address, ['street', 'text']), - 'pobox': GetListEntryField(address, ['pobox', 'text']), - 'neighborhood': GetListEntryField(address, ['neighborhood', 'text']), - 'city': GetListEntryField(address, ['city', 'text']), - 'region': GetListEntryField(address, ['region', 'text']), - 'postcode': GetListEntryField(address, ['postcode', 'text']), - 'country': GetListEntryField(address, ['country', 'text']), - 'primary': address.primary}) - for calendarLink in contactEntry.calendarLink: - AppendItemToFieldsList(CONTACT_CALENDARS, - {'rel': calendarLink.rel, - 'label': calendarLink.label, - 'value': calendarLink.href, - 'primary': calendarLink.primary}) - for emailaddr in contactEntry.email: - AppendItemToFieldsList(CONTACT_EMAILS, - {'rel': emailaddr.rel, - 'label': emailaddr.label, - 'value': emailaddr.address, - 'primary': emailaddr.primary}) - for externalid in contactEntry.externalId: - AppendItemToFieldsList(CONTACT_EXTERNALIDS, - {'rel': externalid.rel, - 'label': externalid.label, - 'value': externalid.value}) - for event in contactEntry.event: - AppendItemToFieldsList(CONTACT_EVENTS, - {'rel': event.rel, - 'label': event.label, - 'value': GetListEntryField(event, ['when', 'startTime'])}) - for hobby in contactEntry.hobby: - AppendItemToFieldsList(CONTACT_HOBBIES, - {'value': hobby.text}) - for im in contactEntry.im: - AppendItemToFieldsList(CONTACT_IMS, - {'rel': im.rel, - 'label': im.label, - 'value': im.address, - 'protocol': im.protocol, - 'primary': im.primary}) - for jot in contactEntry.jot: - AppendItemToFieldsList(CONTACT_JOTS, - {'rel': jot.rel, - 'value': jot.text}) - for organization in contactEntry.organization: - AppendItemToFieldsList(CONTACT_ORGANIZATIONS, - {'rel': organization.rel, - 'label': organization.label, - 'value': GetListEntryField(organization, ['name', 'text']), - 'department': GetListEntryField(organization, ['department', 'text']), - 'title': GetListEntryField(organization, ['title', 'text']), - 'symbol': GetListEntryField(organization, ['symbol', 'text']), - 'jobdescription': GetListEntryField(organization, ['job_description', 'text']), - 'where': GetListEntryField(organization, ['where', 'value_string']), - 'primary': organization.primary}) - for phone in contactEntry.phoneNumber: - AppendItemToFieldsList(CONTACT_PHONES, - {'rel': phone.rel, - 'label': phone.label, - 'value': phone.text, - 'primary': phone.primary}) - for relation in contactEntry.relation: - AppendItemToFieldsList(CONTACT_RELATIONS, - {'rel': relation.rel, - 'label': relation.label, - 'value': relation.text}) - for userdefinedfield in contactEntry.userDefinedField: - AppendItemToFieldsList(CONTACT_USER_DEFINED_FIELDS, - {'rel': userdefinedfield.key, - 'value': userdefinedfield.value}) - for website in contactEntry.website: - AppendItemToFieldsList(CONTACT_WEBSITES, - {'rel': website.rel, - 'label': website.label, - 'value': website.href, - 'primary': website.primary}) - return fields +# Contact query parameters (shared with people.py) CONTACTS_PROJECTION_CHOICE_MAP = {'basic': 'thin', 'thin': 'thin', 'full': 'full'} CONTACTS_ORDERBY_CHOICE_MAP = {'lastmodified': 'lastmodified'} -def normalizeContactId(contactId): - if contactId.startswith('id:'): - return contactId[3:] - return contactId - -def _initContactQueryAttributes(): - return {'query': None, 'projection': 'full', 'url_params': {'max-results': str(GC.Values[GC.CONTACT_MAX_RESULTS])}, - 'emailMatchPattern': None, 'emailMatchType': None} - -def _getContactQueryAttributes(contactQuery, myarg, unknownAction, printShowCmd): - if myarg == 'query': - contactQuery['query'] = getString(Cmd.OB_QUERY) - elif myarg == 'emailmatchpattern': - contactQuery['emailMatchPattern'] = getREPattern(re.IGNORECASE) - elif myarg == 'emailmatchtype': - contactQuery['emailMatchType'] = getString(Cmd.OB_CONTACT_EMAIL_TYPE) - elif myarg == 'updatedmin': - contactQuery['url_params']['updated-min'] = getYYYYMMDD() - elif myarg == 'endquery': - return False - elif not printShowCmd: - if unknownAction < 0: - unknownArgumentExit() - if unknownAction > 0: - Cmd.Backup() - return False - elif myarg == 'orderby': - contactQuery['url_params']['orderby'], contactQuery['url_params']['sortorder'] = getOrderBySortOrder(CONTACTS_ORDERBY_CHOICE_MAP, 'ascending', False) - elif myarg in CONTACTS_PROJECTION_CHOICE_MAP: - contactQuery['projection'] = CONTACTS_PROJECTION_CHOICE_MAP[myarg] - elif myarg == 'showdeleted': - contactQuery['url_params']['showdeleted'] = 'true' - else: - if unknownAction < 0: - unknownArgumentExit() - if unknownAction > 0: - Cmd.Backup() - return False - return True - -CONTACT_SELECT_ARGUMENTS = {'query', 'emailmatchpattern', 'emailmatchtype', 'updatedmin'} - -def _getContactEntityList(unknownAction, printShowCmd): - contactQuery = _initContactQueryAttributes() - if Cmd.PeekArgumentPresent(CONTACT_SELECT_ARGUMENTS): - entityList = None - queriedContacts = True - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if not _getContactQueryAttributes(contactQuery, myarg, unknownAction, printShowCmd): - break - else: - entityList = getEntityList(Cmd.OB_CONTACT_ENTITY) - queriedContacts = False - if unknownAction < 0: - checkForExtraneousArguments() - return (entityList, contactQuery, queriedContacts) - -def queryContacts(contactsObject, contactQuery): - entityType = Ent.DOMAIN - user = GC.Values[GC.DOMAIN] - if contactQuery['query']: - uri = getContactsQuery(feed=contactsObject.GetContactFeedUri(contact_list=user, projection=contactQuery['projection']), - text_query=contactQuery['query']).ToUri() - else: - uri = contactsObject.GetContactFeedUri(contact_list=user, projection=contactQuery['projection']) - printGettingAllEntityItemsForWhom(Ent.CONTACT, user, query=contactQuery['query']) - try: - entityList = callGDataPages(contactsObject, 'GetContactsFeed', - pageMessage=getPageMessageForWhom(), - throwErrors=[GDATA.BAD_REQUEST, GDATA.FORBIDDEN], - retryErrors=[GDATA.INTERNAL_SERVER_ERROR], - uri=uri, url_params=contactQuery['url_params']) - return entityList - except GDATA.badRequest as e: - entityActionFailedWarning([entityType, user, Ent.CONTACT, ''], str(e)) - except GDATA.forbidden: - entityServiceNotApplicableWarning(entityType, user) - return None - -def localContactSelects(contactsManager, contactQuery, fields): - if contactQuery['emailMatchPattern']: - emailMatchType = contactQuery['emailMatchType'] - for item in fields.get(CONTACT_EMAILS, []): - if contactQuery['emailMatchPattern'].match(item['value']): - if (not emailMatchType or - emailMatchType == item.get('label') or - emailMatchType == contactsManager.CONTACT_ARRAY_PROPERTIES[CONTACT_EMAILS]['relMap'].get(item['rel'], 'custom')): - break - else: - return False - return True - -def countLocalContactSelects(contactsManager, contacts, contactQuery): - if contacts is not None and contactQuery: - jcount = 0 - for contact in contacts: - fields = contactsManager.ContactToFields(contact) - if localContactSelects(contactsManager, contactQuery, fields): - jcount += 1 - else: - jcount = len(contacts) if contacts is not None else 0 - return jcount - -def clearEmailAddressMatches(contactsManager, contactClear, fields): - savedAddresses = [] - updateRequired = False - emailMatchType = contactClear['emailClearType'] - for item in fields.get(CONTACT_EMAILS, []): - if (contactClear['emailClearPattern'].match(item['value']) and - (not emailMatchType or - emailMatchType == item.get('label') or - emailMatchType == contactsManager.CONTACT_ARRAY_PROPERTIES[CONTACT_EMAILS]['relMap'].get(item['rel'], 'custom'))): - updateRequired = True - else: - savedAddresses.append(item) - if updateRequired: - fields[CONTACT_EMAILS] = savedAddresses - return updateRequired - -def dedupEmailAddressMatches(contactsManager, emailMatchType, fields): - sai = -1 - savedAddresses = [] - matches = {} - updateRequired = False - for item in fields.get(CONTACT_EMAILS, []): - emailAddr = item['value'] - emailType = item.get('label') - if emailType is None: - emailType = contactsManager.CONTACT_ARRAY_PROPERTIES[CONTACT_EMAILS]['relMap'].get(item['rel'], 'custom') - if (emailAddr in matches) and (not emailMatchType or emailType in matches[emailAddr]['types']): - if item['primary'] == 'true': - savedAddresses[matches[emailAddr]['sai']]['primary'] = 'true' - updateRequired = True - else: - savedAddresses.append(item) - sai += 1 - matches.setdefault(emailAddr, {'types': set(), 'sai': sai}) - matches[emailAddr]['types'].add(emailType) - if updateRequired: - fields[CONTACT_EMAILS] = savedAddresses - return updateRequired - -# gam create contact + -# [(csv [todrive *] (addcsvdata )*))| returnidonly] -def doCreateDomainContact(): - entityType = Ent.DOMAIN - contactsManager = ContactsManager() - parameters = {'csvPF': None, 'titles': ['Domain', CONTACT_ID], 'addCSVData': {}, 'returnIdOnly': False} - fields = contactsManager.GetContactFields(parameters) - csvPF = parameters['csvPF'] - addCSVData = parameters['addCSVData'] - if addCSVData: - csvPF.AddTitles(sorted(addCSVData.keys())) - returnIdOnly = parameters['returnIdOnly'] - contactEntry = contactsManager.FieldsToContact(fields) - user, contactsObject = getContactsObject() - try: - contact = callGData(contactsObject, 'CreateContact', - throwErrors=[GDATA.BAD_REQUEST, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN], - retryErrors=[GDATA.INTERNAL_SERVER_ERROR], - new_contact=contactEntry, insert_uri=contactsObject.GetContactFeedUri(contact_list=user)) - contactId = contactsManager.GetContactShortId(contact) - if returnIdOnly: - writeStdout(f'{contactId}\n') - elif not csvPF: - entityActionPerformed([entityType, user, Ent.CONTACT, contactId]) - else: - row = {'Domain': user, CONTACT_ID: contactId} - if addCSVData: - row.update(addCSVData) - csvPF.WriteRow(row) - except GDATA.badRequest as e: - entityActionFailedWarning([entityType, user, Ent.CONTACT, ''], str(e)) - except GDATA.forbidden: - entityServiceNotApplicableWarning(entityType, user) - except GDATA.serviceNotApplicable: - entityUnknownWarning(entityType, user) - if csvPF: - csvPF.writeCSVfile('Contacts') - -def _clearUpdateContacts(updateContacts): - entityType = Ent.DOMAIN - contactsManager = ContactsManager() - entityList, contactQuery, queriedContacts = _getContactEntityList(1, False) - if updateContacts: - update_fields = contactsManager.GetContactFields() - else: - contactClear = {'emailClearPattern': contactQuery['emailMatchPattern'], 'emailClearType': contactQuery['emailMatchType']} - deleteClearedContactsWithNoEmails = False - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if myarg == 'emailclearpattern': - contactClear['emailClearPattern'] = getREPattern(re.IGNORECASE) - elif myarg == 'emailcleartype': - contactClear['emailClearType'] = getString(Cmd.OB_CONTACT_EMAIL_TYPE) - elif myarg == 'deleteclearedcontactswithnoemails': - deleteClearedContactsWithNoEmails = True - else: - unknownArgumentExit() - if not contactClear['emailClearPattern']: - missingArgumentExit('emailclearpattern') - user, contactsObject = getContactsObject() - if queriedContacts: - entityList = queryContacts(contactsObject, contactQuery) - if entityList is None: - return - j = 0 - jcount = len(entityList) - entityPerformActionModifierNumItems([entityType, user], Msg.MAXIMUM_OF, jcount, Ent.CONTACT) - if jcount == 0: - setSysExitRC(NO_ENTITIES_FOUND_RC) - return - Ind.Increment() - for contact in entityList: - j += 1 - try: - if not queriedContacts: - contactId = normalizeContactId(contact) - contact = callGData(contactsObject, 'GetContact', - throwErrors=[GDATA.NOT_FOUND, GDATA.BAD_REQUEST, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN, GDATA.NOT_IMPLEMENTED], - retryErrors=[GDATA.INTERNAL_SERVER_ERROR], - uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId)) - fields = contactsManager.ContactToFields(contact) - else: - contactId = contactsManager.GetContactShortId(contact) - fields = contactsManager.ContactToFields(contact) - if not localContactSelects(contactsManager, contactQuery, fields): - continue - if updateContacts: -##### Zip - for field, value in update_fields.items(): - fields[field] = value - contactEntry = contactsManager.FieldsToContact(fields) - else: - if not clearEmailAddressMatches(contactsManager, contactClear, fields): - continue - if deleteClearedContactsWithNoEmails and not fields[CONTACT_EMAILS]: - Act.Set(Act.DELETE) - callGData(contactsObject, 'DeleteContact', - throwErrors=[GDATA.NOT_FOUND, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN], - edit_uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId), extra_headers={'If-Match': contact.etag}) - entityActionPerformed([entityType, user, Ent.CONTACT, contactId], j, jcount) - continue - contactEntry = contactsManager.FieldsToContact(fields) - contactEntry.category = contact.category - contactEntry.link = contact.link - contactEntry.etag = contact.etag - contactEntry.id = contact.id - Act.Set(Act.UPDATE) - callGData(contactsObject, 'UpdateContact', - throwErrors=[GDATA.NOT_FOUND, GDATA.BAD_REQUEST, GDATA.PRECONDITION_FAILED, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN], - edit_uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId), updated_contact=contactEntry, extra_headers={'If-Match': contact.etag}) - entityActionPerformed([entityType, user, Ent.CONTACT, contactId], j, jcount) - except (GDATA.notFound, GDATA.badRequest, GDATA.preconditionFailed) as e: - entityActionFailedWarning([entityType, user, Ent.CONTACT, contactId], str(e), j, jcount) - except (GDATA.forbidden, GDATA.notImplemented): - entityServiceNotApplicableWarning(entityType, user) - break - except GDATA.serviceNotApplicable: - entityUnknownWarning(entityType, user) - break - Ind.Decrement() - -# gam clear contacts | -# [clearmatchpattern ] [clearmatchtype work|home|other|] -# [deleteclearedcontactswithnoemails] -def doClearDomainContacts(): - _clearUpdateContacts(False) - -# gam update contacts | + -def doUpdateDomainContacts(): - _clearUpdateContacts(True) - -# gam dedup contacts | [matchType []] -def doDedupDomainContacts(): - entityType = Ent.DOMAIN - contactsManager = ContactsManager() - contactQuery = _initContactQueryAttributes() - emailMatchType = False - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if myarg == 'matchtype': - emailMatchType = getBoolean() - else: - _getContactQueryAttributes(contactQuery, myarg, -1, False) - user, contactsObject = getContactsObject() - contacts = queryContacts(contactsObject, contactQuery) - if contacts is None: - return - j = 0 - jcount = len(contacts) - entityPerformActionModifierNumItems([entityType, user], Msg.MAXIMUM_OF, jcount, Ent.CONTACT) - if jcount == 0: - setSysExitRC(NO_ENTITIES_FOUND_RC) - return - Ind.Increment() - for contact in contacts: - j += 1 - try: - fields = contactsManager.ContactToFields(contact) - if not localContactSelects(contactsManager, contactQuery, fields): - continue - if not dedupEmailAddressMatches(contactsManager, emailMatchType, fields): - continue - contactId = fields[CONTACT_ID] - contactEntry = contactsManager.FieldsToContact(fields) - contactEntry.category = contact.category - contactEntry.link = contact.link - contactEntry.etag = contact.etag - contactEntry.id = contact.id - Act.Set(Act.UPDATE) - callGData(contactsObject, 'UpdateContact', - throwErrors=[GDATA.NOT_FOUND, GDATA.BAD_REQUEST, GDATA.PRECONDITION_FAILED, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN], - edit_uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId), updated_contact=contactEntry, extra_headers={'If-Match': contact.etag}) - entityActionPerformed([entityType, user, Ent.CONTACT, contactId], j, jcount) - except (GDATA.notFound, GDATA.badRequest, GDATA.preconditionFailed) as e: - entityActionFailedWarning([entityType, user, Ent.CONTACT, contactId], str(e), j, jcount) - except (GDATA.forbidden, GDATA.notImplemented): - entityServiceNotApplicableWarning(entityType, user) - break - except GDATA.serviceNotApplicable: - entityUnknownWarning(entityType, user) - break - Ind.Decrement() - -# gam delete contacts | -def doDeleteDomainContacts(): - entityType = Ent.DOMAIN - contactsManager = ContactsManager() - entityList, contactQuery, queriedContacts = _getContactEntityList(-1, False) - user, contactsObject = getContactsObject() - if queriedContacts: - entityList = queryContacts(contactsObject, contactQuery) - if entityList is None: - return - j = 0 - jcount = len(entityList) - entityPerformActionModifierNumItems([entityType, user], Msg.MAXIMUM_OF, jcount, Ent.CONTACT) - if jcount == 0: - setSysExitRC(NO_ENTITIES_FOUND_RC) - return - Ind.Increment() - for contact in entityList: - j += 1 - try: - if not queriedContacts: - contactId = normalizeContactId(contact) - contact = callGData(contactsObject, 'GetContact', - throwErrors=[GDATA.NOT_FOUND, GDATA.BAD_REQUEST, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN, GDATA.NOT_IMPLEMENTED], - retryErrors=[GDATA.INTERNAL_SERVER_ERROR], - uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId)) - else: - contactId = contactsManager.GetContactShortId(contact) - fields = contactsManager.ContactToFields(contact) - if not localContactSelects(contactsManager, contactQuery, fields): - continue - callGData(contactsObject, 'DeleteContact', - throwErrors=[GDATA.NOT_FOUND, GDATA.SERVICE_NOT_APPLICABLE, GDATA.FORBIDDEN], - edit_uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId), extra_headers={'If-Match': contact.etag}) - entityActionPerformed([entityType, user, Ent.CONTACT, contactId], j, jcount) - except (GDATA.notFound, GDATA.badRequest) as e: - entityActionFailedWarning([entityType, user, Ent.CONTACT, contactId], str(e), j, jcount) - except (GDATA.forbidden, GDATA.notImplemented): - entityServiceNotApplicableWarning(entityType, user) - break - except GDATA.serviceNotApplicable: - entityUnknownWarning(entityType, user) - break - Ind.Decrement() - -CONTACT_TIME_OBJECTS = {CONTACT_UPDATED} -CONTACT_FIELDS_WITH_CRS_NLS = {CONTACT_NOTES, CONTACT_BILLING_INFORMATION} - -def _showContact(contactsManager, fields, displayFieldsList, j, jcount, FJQC): - if FJQC.formatJSON: - printLine(json.dumps(cleanJSON(fields, timeObjects=CONTACT_TIME_OBJECTS), ensure_ascii=False, sort_keys=True)) - return - printEntity([Ent.CONTACT, fields[CONTACT_ID]], j, jcount) - Ind.Increment() - for key in contactsManager.CONTACT_NAME_PROPERTY_PRINT_ORDER: - if displayFieldsList and key not in displayFieldsList: - continue - if key in fields: - if key in CONTACT_TIME_OBJECTS: - printKeyValueList([key, formatLocalTime(fields[key])]) - elif key not in CONTACT_FIELDS_WITH_CRS_NLS: - printKeyValueList([key, fields[key]]) - else: - printKeyValueWithCRsNLs(key, fields[key]) - for key in contactsManager.CONTACT_ARRAY_PROPERTY_PRINT_ORDER: - if displayFieldsList and key not in displayFieldsList: - continue - if key in fields: - keymap = contactsManager.CONTACT_ARRAY_PROPERTIES[key] - printKeyValueList([key, None]) - Ind.Increment() - for item in fields[key]: - fn = item.get('label') - if keymap['relMap']: - if not fn: - fn = keymap['relMap'].get(item['rel'], 'custom') - printKeyValueList(['type', fn]) - Ind.Increment() - if keymap['primary']: - printKeyValueList(['rank', ['notprimary', 'primary'][item['primary'] == 'true']]) - value = item['value'] - if value is None: - value = '' - if key == CONTACT_IMS: - printKeyValueList(['protocol', contactsManager.IM_REL_TO_PROTOCOL_MAP.get(item['protocol'], item['protocol'])]) - printKeyValueList([keymap['infoTitle'], value]) - elif key == CONTACT_ADDRESSES: - printKeyValueWithCRsNLs(keymap['infoTitle'], value) - for org_key in contactsManager.ADDRESS_FIELD_PRINT_ORDER: - if item[org_key]: - printKeyValueList([contactsManager.ADDRESS_FIELD_TO_ARGUMENT_MAP[org_key], item[org_key]]) - elif key == CONTACT_ORGANIZATIONS: - printKeyValueList([keymap['infoTitle'], value]) - for org_key in contactsManager.ORGANIZATION_FIELD_PRINT_ORDER: - if item[org_key]: - printKeyValueList([contactsManager.ORGANIZATION_FIELD_TO_ARGUMENT_MAP[org_key], item[org_key]]) - elif key == CONTACT_USER_DEFINED_FIELDS: - printKeyValueList([item.get('rel') or 'None', value]) - else: - printKeyValueList([keymap['infoTitle'], value]) - if keymap['relMap']: - Ind.Decrement() - Ind.Decrement() - Ind.Decrement() - -def _getContactFieldsList(contactsManager, displayFieldsList): - for field in _getFieldsList(): - if field in contactsManager.CONTACT_ARGUMENT_TO_PROPERTY_MAP: - displayFieldsList.append(contactsManager.CONTACT_ARGUMENT_TO_PROPERTY_MAP[field]) - else: - invalidChoiceExit(field, contactsManager.CONTACT_ARGUMENT_TO_PROPERTY_MAP, True) - -# gam info contacts -# [basic|full] -# [fields ] [formatjson] -def doInfoDomainContacts(): - entityType = Ent.DOMAIN - contactsManager = ContactsManager() - entityList = getEntityList(Cmd.OB_CONTACT_ENTITY) - contactQuery = _initContactQueryAttributes() - FJQC = FormatJSONQuoteChar() - displayFieldsList = [] - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if myarg in CONTACTS_PROJECTION_CHOICE_MAP: - contactQuery['projection'] = CONTACTS_PROJECTION_CHOICE_MAP[myarg] - elif myarg == 'fields': - _getContactFieldsList(contactsManager, displayFieldsList) - else: - FJQC.GetFormatJSON(myarg) - user, contactsObject = getContactsObject() - j = 0 - jcount = len(entityList) - if not FJQC.formatJSON: - entityPerformActionNumItems([entityType, user], jcount, Ent.CONTACT) - if jcount == 0: - setSysExitRC(NO_ENTITIES_FOUND_RC) - return - Ind.Increment() - for contact in entityList: - j += 1 - try: - contactId = normalizeContactId(contact) - contact = callGData(contactsObject, 'GetContact', - bailOnInternalServerError=True, - throwErrors=[GDATA.NOT_FOUND, GDATA.BAD_REQUEST, GDATA.SERVICE_NOT_APPLICABLE, - GDATA.FORBIDDEN, GDATA.NOT_IMPLEMENTED, GDATA.INTERNAL_SERVER_ERROR], - retryErrors=[GDATA.INTERNAL_SERVER_ERROR], - uri=contactsObject.GetContactFeedUri(contact_list=user, contactId=contactId, projection=contactQuery['projection'])) - fields = contactsManager.ContactToFields(contact) - _showContact(contactsManager, fields, displayFieldsList, j, jcount, FJQC) - except (GDATA.notFound, GDATA.badRequest, GDATA.forbidden, GDATA.notImplemented, GDATA.internalServerError) as e: - entityActionFailedWarning([entityType, user, Ent.CONTACT, contactId], str(e), j, jcount) - except GDATA.serviceNotApplicable: - entityUnknownWarning(entityType, user) - break - Ind.Decrement() - -# gam print contacts [todrive *] -# [basic|full|countsonly] [showdeleted] [orderby [ascending|descending]] -# [fields ] [formatjson [quotechar ]] -# gam show contacts -# [basic|full|countsonly] [showdeleted] [orderby [ascending|descending]] -# [fields ] [formatjson] -def doPrintShowDomainContacts(): - entityType = Ent.DOMAIN - entityTypeName = Ent.Singular(entityType) - contactsManager = ContactsManager() - csvPF = CSVPrintFile([entityTypeName, CONTACT_ID, CONTACT_NAME], 'sortall', - contactsManager.CONTACT_ARRAY_PROPERTY_PRINT_ORDER) if Act.csvFormat() else None - FJQC = FormatJSONQuoteChar(csvPF) - CSVTitle = 'Contacts' - contactQuery = _initContactQueryAttributes() - countsOnly = False - displayFieldsList = [] - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if csvPF and myarg == 'todrive': - csvPF.GetTodriveParameters() - elif myarg == 'fields': - _getContactFieldsList(contactsManager, displayFieldsList) - elif myarg == 'countsonly': - countsOnly = True - contactQuery['projection'] = CONTACTS_PROJECTION_CHOICE_MAP['basic'] - if csvPF: - csvPF.SetTitles([entityTypeName, CSVTitle]) - elif _getContactQueryAttributes(contactQuery, myarg, 0, True): - pass - else: - FJQC.GetFormatJSONQuoteChar(myarg, True) - user, contactsObject = getContactsObject() - contacts = queryContacts(contactsObject, contactQuery) - if countsOnly: - jcount = countLocalContactSelects(contactsManager, contacts, contactQuery) - if csvPF: - csvPF.WriteRowTitles({entityTypeName: user, CSVTitle: jcount}) - else: - printEntityKVList([entityType, user], [CSVTitle, jcount]) - elif contacts is not None: - jcount = len(contacts) - if not csvPF: - if not FJQC.formatJSON: - entityPerformActionModifierNumItems([entityType, user], Msg.MAXIMUM_OF, jcount, Ent.CONTACT) - Ind.Increment() - j = 0 - for contact in contacts: - j += 1 - fields = contactsManager.ContactToFields(contact) - if not localContactSelects(contactsManager, contactQuery, fields): - continue - _showContact(contactsManager, fields, displayFieldsList, j, jcount, FJQC) - Ind.Decrement() - elif contacts: - for contact in contacts: - fields = contactsManager.ContactToFields(contact) - if not localContactSelects(contactsManager, contactQuery, fields): - continue - contactRow = {entityTypeName: user, CONTACT_ID: fields[CONTACT_ID]} - for key in contactsManager.CONTACT_NAME_PROPERTY_PRINT_ORDER: - if displayFieldsList and key not in displayFieldsList: - continue - if key in fields: - if key == CONTACT_UPDATED: - contactRow[key] = formatLocalTime(fields[key]) - elif key not in (CONTACT_NOTES, CONTACT_BILLING_INFORMATION): - contactRow[key] = fields[key] - else: - contactRow[key] = escapeCRsNLs(fields[key]) - for key in contactsManager.CONTACT_ARRAY_PROPERTY_PRINT_ORDER: - if displayFieldsList and key not in displayFieldsList: - continue - if key in fields: - keymap = contactsManager.CONTACT_ARRAY_PROPERTIES[key] - j = 0 - contactRow[f'{key}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}count'] = len(fields[key]) - for item in fields[key]: - j += 1 - fn = f'{key}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{j}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}' - fnt = item.get('label') - if fnt: - contactRow[fn+'type'] = fnt - elif keymap['relMap']: - contactRow[fn+'type'] = keymap['relMap'].get(item['rel'], 'custom') - if keymap['primary']: - contactRow[fn+'rank'] = 'primary' if item['primary'] == 'true' else 'notprimary' - value = item['value'] - if value is None: - value = '' - if key == CONTACT_IMS: - contactRow[fn+'protocol'] = contactsManager.IM_REL_TO_PROTOCOL_MAP.get(item['protocol'], item['protocol']) - contactRow[fn+keymap['infoTitle']] = value - elif key == CONTACT_ADDRESSES: - contactRow[fn+keymap['infoTitle']] = escapeCRsNLs(value) - for org_key in contactsManager.ADDRESS_FIELD_PRINT_ORDER: - if item[org_key]: - contactRow[fn+contactsManager.ADDRESS_FIELD_TO_ARGUMENT_MAP[org_key]] = escapeCRsNLs(item[org_key]) - elif key == CONTACT_ORGANIZATIONS: - contactRow[fn+keymap['infoTitle']] = value - for org_key in contactsManager.ORGANIZATION_FIELD_PRINT_ORDER: - if item[org_key]: - contactRow[fn+contactsManager.ORGANIZATION_FIELD_TO_ARGUMENT_MAP[org_key]] = item[org_key] - elif key == CONTACT_USER_DEFINED_FIELDS: - contactRow[fn+'type'] = item.get('rel') or 'None' - contactRow[fn+keymap['infoTitle']] = value - else: - contactRow[fn+keymap['infoTitle']] = value - if not FJQC.formatJSON: - csvPF.WriteRowTitles(contactRow) - elif csvPF.CheckRowTitles(contactRow): - csvPF.WriteRowNoFilter({entityTypeName: user, CONTACT_ID: fields[CONTACT_ID], - CONTACT_NAME: fields.get(CONTACT_NAME, ''), - 'JSON': json.dumps(cleanJSON(fields, timeObjects=CONTACT_TIME_OBJECTS), - ensure_ascii=False, sort_keys=True)}) - if csvPF: - csvPF.writeCSVfile(CSVTitle) - # Prople commands utilities # def normalizePeopleResourceName(resourceName): diff --git a/src/gam/gamlib/api.py b/src/gam/gamlib/api.py index a60fe71f..845f989c 100644 --- a/src/gam/gamlib/api.py +++ b/src/gam/gamlib/api.py @@ -54,7 +54,6 @@ CLOUDIDENTITY_POLICY = 'cloudidentitypolicy' CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations' CLOUDRESOURCEMANAGER = 'cloudresourcemanager' CLOUDRESOURCEMANAGERV1 = 'cloudresourcemanagerv1' -CONTACTS = 'contacts' CONTACTDELEGATION = 'contactdelegation' DATATRANSFER = 'datatransfer' DIRECTORY = 'directory' @@ -66,7 +65,6 @@ DRIVEACTIVITY = 'driveactivity' DRIVELABELS = 'drivelabels' DRIVELABELS_ADMIN = 'drivelabelsadmin' DRIVELABELS_USER = 'drivelabelsuser' -EMAIL_AUDIT = 'email-audit' FORMS = 'forms' GMAIL = 'gmail' GROUPSMIGRATION = 'groupsmigration' @@ -263,7 +261,6 @@ _INFO = { CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDRESOURCEMANAGER: {'name': 'Resource Manager API v3', 'version': 'v3', 'v2discovery': True}, CLOUDRESOURCEMANAGERV1: {'name': 'Resource Manager API v1', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudresourcemanager'}, - CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False}, CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True}, DATATRANSFER: {'name': 'Data Transfer API', 'version': 'datatransfer_v1', 'v2discovery': True, 'mappedAPI': 'admin'}, DIRECTORY: {'name': 'Directory API', 'version': 'directory_v1', 'v2discovery': True, 'mappedAPI': 'admin'}, @@ -274,7 +271,6 @@ _INFO = { DRIVEACTIVITY: {'name': 'Drive Activity API v2', 'version': 'v2', 'v2discovery': True}, DRIVELABELS_ADMIN: {'name': 'Drive Labels API - Admin', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS}, DRIVELABELS_USER: {'name': 'Drive Labels API - User', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS}, - EMAIL_AUDIT: {'name': 'Email Audit API', 'version': 'v1', 'v2discovery': False}, FORMS: {'name': 'Forms API', 'version': 'v1', 'v2discovery': True}, GMAIL: {'name': 'Gmail API', 'version': 'v1', 'v2discovery': True}, GROUPSMIGRATION: {'name': 'Groups Migration API', 'version': 'v1', 'v2discovery': True}, @@ -415,9 +411,6 @@ _CLIENT_SCOPES = [ 'api': STORAGEWRITE, 'offByDefault': True, 'scope': STORAGE_READWRITE_SCOPE}, - {'name': 'Contacts API - Domain Shared Contacts', - 'api': CONTACTS, - 'scope': 'https://www.google.com/m8/feeds'}, {'name': 'Contact Delegation API', 'api': CONTACTDELEGATION, 'subscopes': READONLY, @@ -469,10 +462,6 @@ _CLIENT_SCOPES = [ 'api': DIRECTORY, 'subscopes': READONLY, 'scope': 'https://www.googleapis.com/auth/admin.directory.user'}, - {'name': 'Email Audit API', - 'api': EMAIL_AUDIT, - 'offByDefault': True, - 'scope': 'https://apps-apis.google.com/a/feeds/compliance/audit/'}, {'name': 'Groups Migration API', 'api': GROUPSMIGRATION, 'scope': 'https://www.googleapis.com/auth/apps.groups.migration'}, @@ -637,11 +626,7 @@ _SVCACCT_SCOPES = [ # 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, # {'name': 'Cloud Identity User Invitations API', # 'api': CLOUDIDENTITY_USERINVITATIONS, -# 'subscopes': READONLY, # 'scope': 'https://www.googleapis.com/auth/cloud-identity'}, -# {'name': 'Contacts API - Users', -# 'api': CONTACTS, -# 'scope': 'https://www.google.com/m8/feeds'}, {'name': 'Drive API', 'api': DRIVE3, 'subscopes': READONLY, diff --git a/src/gam/gamlib/gdata.py b/src/gam/gamlib/gdata.py deleted file mode 100644 index d1313e02..00000000 --- a/src/gam/gamlib/gdata.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2023 Ross Scroggs All Rights Reserved. -# -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""GAM GData resources - -""" -API_DEPRECATED_MSG = 'Contacts API is being deprecated.' - -# callGData throw errors -API_DEPRECATED = 612 -BAD_GATEWAY = 601 -BAD_REQUEST = 602 -DOES_NOT_EXIST = 1301 -ENTITY_EXISTS = 1300 -FORBIDDEN = 603 -GATEWAY_TIMEOUT = 612 -INSUFFICIENT_PERMISSIONS = 604 -INTERNAL_SERVER_ERROR = 1000 -INVALID_DOMAIN = 605 -INVALID_INPUT = 1317 -INVALID_VALUE = 1801 -NAME_NOT_VALID = 1303 -NOT_FOUND = 606 -NOT_IMPLEMENTED = 607 -PRECONDITION_FAILED = 608 -QUOTA_EXCEEDED = 609 -SERVICE_NOT_APPLICABLE = 1410 -SERVICE_UNAVAILABLE = 610 -TOKEN_EXPIRED = 611 -TOKEN_INVALID = 403 -UNKNOWN_ERROR = 600 -# -NON_TERMINATING_ERRORS = [API_DEPRECATED, BAD_GATEWAY, GATEWAY_TIMEOUT, QUOTA_EXCEEDED, SERVICE_UNAVAILABLE, TOKEN_EXPIRED] -EMAILSETTINGS_THROW_LIST = [INVALID_DOMAIN, DOES_NOT_EXIST, SERVICE_NOT_APPLICABLE, BAD_REQUEST, NAME_NOT_VALID, INTERNAL_SERVER_ERROR, INVALID_VALUE] -# -class apiDeprecated(Exception): - pass -class badRequest(Exception): - pass -class doesNotExist(Exception): - pass -class entityExists(Exception): - pass -class forbidden(Exception): - pass -class insufficientPermissions(Exception): - pass -class internalServerError(Exception): - pass -class invalidDomain(Exception): - pass -class invalidInput(Exception): - pass -class invalidValue(Exception): - pass -class nameNotValid(Exception): - pass -class notFound(Exception): - pass -class notImplemented(Exception): - pass -class preconditionFailed(Exception): - pass -class serviceNotApplicable(Exception): - pass - -ERROR_CODE_EXCEPTION_MAP = { - API_DEPRECATED: apiDeprecated, - BAD_REQUEST: badRequest, - DOES_NOT_EXIST: doesNotExist, - ENTITY_EXISTS: entityExists, - FORBIDDEN: forbidden, - INSUFFICIENT_PERMISSIONS: insufficientPermissions, - INTERNAL_SERVER_ERROR: internalServerError, - INVALID_DOMAIN: invalidDomain, - INVALID_INPUT: invalidInput, - INVALID_VALUE: invalidValue, - NAME_NOT_VALID: nameNotValid, - NOT_FOUND: notFound, - NOT_IMPLEMENTED: notImplemented, - PRECONDITION_FAILED: preconditionFailed, - SERVICE_NOT_APPLICABLE: serviceNotApplicable, - } diff --git a/src/gam/gdata/__init__.py b/src/gam/gdata/__init__.py deleted file mode 100644 index 3468d501..00000000 --- a/src/gam/gdata/__init__.py +++ /dev/null @@ -1,825 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2006 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""Contains classes representing Google Data elements. - - Extends Atom classes to add Google Data specific elements. -""" - - -__author__ = 'j.s@google.com (Jeffrey Scudder)' - -import os -import atom -import lxml.etree as ElementTree - -# XML namespaces which are often used in GData entities. -GDATA_NAMESPACE = 'http://schemas.google.com/g/2005' -GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s' -OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/' -OPENSEARCH_TEMPLATE = '{http://a9.com/-/spec/opensearchrss/1.0/}%s' -BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch' -GACL_NAMESPACE = 'http://schemas.google.com/acl/2007' -GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s' - - -# Labels used in batch request entries to specify the desired CRUD operation. -BATCH_INSERT = 'insert' -BATCH_UPDATE = 'update' -BATCH_DELETE = 'delete' -BATCH_QUERY = 'query' - -class Error(Exception): - pass - - -class MissingRequiredParameters(Error): - pass - - -class MediaSource(object): - """GData Entries can refer to media sources, so this class provides a - place to store references to these objects along with some metadata. - """ - - def __init__(self, file_handle=None, content_type=None, content_length=None, - file_path=None, file_name=None): - """Creates an object of type MediaSource. - - Args: - file_handle: A file handle pointing to the file to be encapsulated in the - MediaSource - content_type: string The MIME type of the file. Required if a file_handle - is given. - content_length: int The size of the file. Required if a file_handle is - given. - file_path: string (optional) A full path name to the file. Used in - place of a file_handle. - file_name: string The name of the file without any path information. - Required if a file_handle is given. - """ - self.file_handle = file_handle - self.content_type = content_type - self.content_length = content_length - self.file_name = file_name - - if (file_handle is None and content_type is not None and - file_path is not None): - self.setFile(file_path, content_type) - - def setFile(self, file_name, content_type): - """A helper function which can create a file handle from a given filename - and set the content type and length all at once. - - Args: - file_name: string The path and file name to the file containing the media - content_type: string A MIME type representing the type of the media - """ - - self.file_handle = open(file_name, 'rb') - self.content_type = content_type - self.content_length = os.path.getsize(file_name) - self.file_name = os.path.basename(file_name) - - -class LinkFinder(atom.LinkFinder): - """An "interface" providing methods to find link elements - - GData Entry elements often contain multiple links which differ in the rel - attribute or content type. Often, developers are interested in a specific - type of link so this class provides methods to find specific classes of - links. - - This class is used as a mixin in GData entries. - """ - - def GetSelfLink(self): - """Find the first link with rel set to 'self' - - Returns: - An atom.Link or none if none of the links had rel equal to 'self' - """ - - for a_link in self.link: - if a_link.rel == 'self': - return a_link - return None - - def GetEditLink(self): - for a_link in self.link: - if a_link.rel == 'edit': - return a_link - return None - - def GetEditMediaLink(self): - """The Picasa API mistakenly returns media-edit rather than edit-media, but - this may change soon. - """ - for a_link in self.link: - if a_link.rel == 'edit-media': - return a_link - if a_link.rel == 'media-edit': - return a_link - return None - - def GetHtmlLink(self): - """Find the first link with rel of alternate and type of text/html - - Returns: - An atom.Link or None if no links matched - """ - for a_link in self.link: - if a_link.rel == 'alternate' and a_link.type == 'text/html': - return a_link - return None - - def GetPostLink(self): - """Get a link containing the POST target URL. - - The POST target URL is used to insert new entries. - - Returns: - A link object with a rel matching the POST type. - """ - for a_link in self.link: - if a_link.rel == 'http://schemas.google.com/g/2005#post': - return a_link - return None - - def GetAclLink(self): - for a_link in self.link: - if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList': - return a_link - return None - - def GetFeedLink(self): - for a_link in self.link: - if a_link.rel == 'http://schemas.google.com/g/2005#feed': - return a_link - return None - - def GetNextLink(self): - for a_link in self.link: - if a_link.rel == 'next': - return a_link - return None - - def GetPrevLink(self): - for a_link in self.link: - if a_link.rel == 'previous': - return a_link - return None - - -class TotalResults(atom.AtomBase): - """opensearch:TotalResults for a GData feed""" - - _tag = 'totalResults' - _namespace = OPENSEARCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - - def __init__(self, extension_elements=None, - extension_attributes=None, text=None): - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def TotalResultsFromString(xml_string): - return atom.CreateClassFromXMLString(TotalResults, xml_string) - - -class StartIndex(atom.AtomBase): - """The opensearch:startIndex element in GData feed""" - - _tag = 'startIndex' - _namespace = OPENSEARCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - - def __init__(self, extension_elements=None, - extension_attributes=None, text=None): - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def StartIndexFromString(xml_string): - return atom.CreateClassFromXMLString(StartIndex, xml_string) - - -class ItemsPerPage(atom.AtomBase): - """The opensearch:itemsPerPage element in GData feed""" - - _tag = 'itemsPerPage' - _namespace = OPENSEARCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - - def __init__(self, extension_elements=None, - extension_attributes=None, text=None): - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def ItemsPerPageFromString(xml_string): - return atom.CreateClassFromXMLString(ItemsPerPage, xml_string) - - -class ExtendedProperty(atom.AtomBase): - """The Google Data extendedProperty element. - - Used to store arbitrary key-value information specific to your - application. The value can either be a text string stored as an XML - attribute (.value), or an XML node (XmlBlob) as a child element. - - This element is used in the Google Calendar data API and the Google - Contacts data API. - """ - - _tag = 'extendedProperty' - _namespace = GDATA_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['name'] = 'name' - _attributes['value'] = 'value' - - def __init__(self, name=None, value=None, extension_elements=None, - extension_attributes=None, text=None): - self.name = name - self.value = value - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - def GetXmlBlobExtensionElement(self): - """Returns the XML blob as an atom.ExtensionElement. - - Returns: - An atom.ExtensionElement representing the blob's XML, or None if no - blob was set. - """ - if len(self.extension_elements) < 1: - return None - else: - return self.extension_elements[0] - - def GetXmlBlobString(self): - """Returns the XML blob as a string. - - Returns: - A string containing the blob's XML, or None if no blob was set. - """ - blob = self.GetXmlBlobExtensionElement() - if blob: - return blob.ToString() - return None - - def SetXmlBlob(self, blob): - """Sets the contents of the extendedProperty to XML as a child node. - - Since the extendedProperty is only allowed one child element as an XML - blob, setting the XML blob will erase any preexisting extension elements - in this object. - - Args: - blob: str, ElementTree Element or atom.ExtensionElement representing - the XML blob stored in the extendedProperty. - """ - # Erase any existing extension_elements, clears the child nodes from the - # extendedProperty. - self.extension_elements = [] - if isinstance(blob, atom.ExtensionElement): - self.extension_elements.append(blob) - elif ElementTree.iselement(blob): - self.extension_elements.append(atom._ExtensionElementFromElementTree( - blob)) - else: - self.extension_elements.append(atom.ExtensionElementFromString(blob)) - - -def ExtendedPropertyFromString(xml_string): - return atom.CreateClassFromXMLString(ExtendedProperty, xml_string) - - -class GDataEntry(atom.Entry, LinkFinder): - """Extends Atom Entry to provide data processing""" - - _tag = atom.Entry._tag - _namespace = atom.Entry._namespace - _children = atom.Entry._children.copy() - _attributes = atom.Entry._attributes.copy() - - def __GetId(self): - return self.__id - - # This method was created to strip the unwanted whitespace from the id's - # text node. - def __SetId(self, id): - self.__id = id - if id is not None and id.text is not None: - self.__id.text = id.text.strip() - - id = property(__GetId, __SetId) - - def IsMedia(self): - """Determines whether or not an entry is a GData Media entry. - """ - if (self.GetEditMediaLink()): - return True - else: - return False - - def GetMediaURL(self): - """Returns the URL to the media content, if the entry is a media entry. - Otherwise returns None. - """ - if not self.IsMedia(): - return None - else: - return self.content.src - - -def GDataEntryFromString(xml_string): - """Creates a new GDataEntry instance given a string of XML.""" - return atom.CreateClassFromXMLString(GDataEntry, xml_string) - - -class GDataFeed(atom.Feed, LinkFinder): - """A Feed from a GData service""" - - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = atom.Feed._children.copy() - _attributes = atom.Feed._attributes.copy() - _children['{%s}totalResults' % OPENSEARCH_NAMESPACE] = ('total_results', - TotalResults) - _children['{%s}startIndex' % OPENSEARCH_NAMESPACE] = ('start_index', - StartIndex) - _children['{%s}itemsPerPage' % OPENSEARCH_NAMESPACE] = ('items_per_page', - ItemsPerPage) - # Add a conversion rule for atom:entry to make it into a GData - # Entry. - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GDataEntry]) - - def __GetId(self): - return self.__id - - def __SetId(self, id): - self.__id = id - if id is not None and id.text is not None: - self.__id.text = id.text.strip() - - id = property(__GetId, __SetId) - - def __GetGenerator(self): - return self.__generator - - def __SetGenerator(self, generator): - self.__generator = generator - if generator is not None: - self.__generator.text = generator.text.strip() - - generator = property(__GetGenerator, __SetGenerator) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, entry=None, - total_results=None, start_index=None, items_per_page=None, - extension_elements=None, extension_attributes=None, text=None): - """Constructor for Source - - Args: - author: list (optional) A list of Author instances which belong to this - class. - category: list (optional) A list of Category instances - contributor: list (optional) A list on Contributor instances - generator: Generator (optional) - icon: Icon (optional) - id: Id (optional) The entry's Id element - link: list (optional) A list of Link instances - logo: Logo (optional) - rights: Rights (optional) The entry's Rights element - subtitle: Subtitle (optional) The entry's subtitle element - title: Title (optional) the entry's title element - updated: Updated (optional) the entry's updated element - entry: list (optional) A list of the Entry instances contained in the - feed. - text: String (optional) The text contents of the element. This is the - contents of the Entry's XML text node. - (Example: This is the text) - extension_elements: list (optional) A list of ExtensionElement instances - which are children of this element. - extension_attributes: dict (optional) A dictionary of strings which are - the values for additional XML attributes of this element. - """ - - self.author = author or [] - self.category = category or [] - self.contributor = contributor or [] - self.generator = generator - self.icon = icon - self.id = atom_id - self.link = link or [] - self.logo = logo - self.rights = rights - self.subtitle = subtitle - self.title = title - self.updated = updated - self.entry = entry or [] - self.total_results = total_results - self.start_index = start_index - self.items_per_page = items_per_page - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def GDataFeedFromString(xml_string): - return atom.CreateClassFromXMLString(GDataFeed, xml_string) - - -class BatchId(atom.AtomBase): - _tag = 'id' - _namespace = BATCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - - -def BatchIdFromString(xml_string): - return atom.CreateClassFromXMLString(BatchId, xml_string) - - -class BatchOperation(atom.AtomBase): - _tag = 'operation' - _namespace = BATCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['type'] = 'type' - - def __init__(self, op_type=None, extension_elements=None, - extension_attributes=None, - text=None): - self.type = op_type - atom.AtomBase.__init__(self, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def BatchOperationFromString(xml_string): - return atom.CreateClassFromXMLString(BatchOperation, xml_string) - - -class BatchStatus(atom.AtomBase): - """The batch:status element present in a batch response entry. - - A status element contains the code (HTTP response code) and - reason as elements. In a single request these fields would - be part of the HTTP response, but in a batch request each - Entry operation has a corresponding Entry in the response - feed which includes status information. - - See http://code.google.com/apis/gdata/batch.html#Handling_Errors - """ - - _tag = 'status' - _namespace = BATCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['code'] = 'code' - _attributes['reason'] = 'reason' - _attributes['content-type'] = 'content_type' - - def __init__(self, code=None, reason=None, content_type=None, - extension_elements=None, extension_attributes=None, text=None): - self.code = code - self.reason = reason - self.content_type = content_type - atom.AtomBase.__init__(self, extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def BatchStatusFromString(xml_string): - return atom.CreateClassFromXMLString(BatchStatus, xml_string) - - -class BatchEntry(GDataEntry): - """An atom:entry for use in batch requests. - - The BatchEntry contains additional members to specify the operation to be - performed on this entry and a batch ID so that the server can reference - individual operations in the response feed. For more information, see: - http://code.google.com/apis/gdata/batch.html - """ - - _tag = GDataEntry._tag - _namespace = GDataEntry._namespace - _children = GDataEntry._children.copy() - _children['{%s}operation' % BATCH_NAMESPACE] = ('batch_operation', BatchOperation) - _children['{%s}id' % BATCH_NAMESPACE] = ('batch_id', BatchId) - _children['{%s}status' % BATCH_NAMESPACE] = ('batch_status', BatchStatus) - _attributes = GDataEntry._attributes.copy() - - def __init__(self, author=None, category=None, content=None, - contributor=None, atom_id=None, link=None, published=None, rights=None, - source=None, summary=None, control=None, title=None, updated=None, - batch_operation=None, batch_id=None, batch_status=None, - extension_elements=None, extension_attributes=None, text=None): - self.batch_operation = batch_operation - self.batch_id = batch_id - self.batch_status = batch_status - GDataEntry.__init__(self, author=author, category=category, - content=content, contributor=contributor, atom_id=atom_id, link=link, - published=published, rights=rights, source=source, summary=summary, - control=control, title=title, updated=updated, - extension_elements=extension_elements, - extension_attributes=extension_attributes, text=text) - - -def BatchEntryFromString(xml_string): - return atom.CreateClassFromXMLString(BatchEntry, xml_string) - - -class BatchInterrupted(atom.AtomBase): - """The batch:interrupted element sent if batch request was interrupted. - - Only appears in a feed if some of the batch entries could not be processed. - See: http://code.google.com/apis/gdata/batch.html#Handling_Errors - """ - - _tag = 'interrupted' - _namespace = BATCH_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['reason'] = 'reason' - _attributes['success'] = 'success' - _attributes['failures'] = 'failures' - _attributes['parsed'] = 'parsed' - - def __init__(self, reason=None, success=None, failures=None, parsed=None, - extension_elements=None, extension_attributes=None, text=None): - self.reason = reason - self.success = success - self.failures = failures - self.parsed = parsed - atom.AtomBase.__init__(self, extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def BatchInterruptedFromString(xml_string): - return atom.CreateClassFromXMLString(BatchInterrupted, xml_string) - - -class BatchFeed(GDataFeed): - """A feed containing a list of batch request entries.""" - - _tag = GDataFeed._tag - _namespace = GDataFeed._namespace - _children = GDataFeed._children.copy() - _attributes = GDataFeed._attributes.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchEntry]) - _children['{%s}interrupted' % BATCH_NAMESPACE] = ('interrupted', BatchInterrupted) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, entry=None, - total_results=None, start_index=None, items_per_page=None, - interrupted=None, - extension_elements=None, extension_attributes=None, text=None): - self.interrupted = interrupted - GDataFeed.__init__(self, author=author, category=category, - contributor=contributor, generator=generator, - icon=icon, atom_id=atom_id, link=link, - logo=logo, rights=rights, subtitle=subtitle, - title=title, updated=updated, entry=entry, - total_results=total_results, start_index=start_index, - items_per_page=items_per_page, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - def AddBatchEntry(self, entry=None, id_url_string=None, - batch_id_string=None, operation_string=None): - """Logic for populating members of a BatchEntry and adding to the feed. - - - If the entry is not a BatchEntry, it is converted to a BatchEntry so - that the batch specific members will be present. - - The id_url_string can be used in place of an entry if the batch operation - applies to a URL. For example query and delete operations require just - the URL of an entry, no body is sent in the HTTP request. If an - id_url_string is sent instead of an entry, a BatchEntry is created and - added to the feed. - - This method also assigns the desired batch id to the entry so that it - can be referenced in the server's response. If the batch_id_string is - None, this method will assign a batch_id to be the index at which this - entry will be in the feed's entry list. - - Args: - entry: BatchEntry, atom.Entry, or another Entry flavor (optional) The - entry which will be sent to the server as part of the batch request. - The item must have a valid atom id so that the server knows which - entry this request references. - id_url_string: str (optional) The URL of the entry to be acted on. You - can find this URL in the text member of the atom id for an entry. - If an entry is not sent, this id will be used to construct a new - BatchEntry which will be added to the request feed. - batch_id_string: str (optional) The batch ID to be used to reference - this batch operation in the results feed. If this parameter is None, - the current length of the feed's entry array will be used as a - count. Note that batch_ids should either always be specified or - never, mixing could potentially result in duplicate batch ids. - operation_string: str (optional) The desired batch operation which will - set the batch_operation.type member of the entry. Options are - 'insert', 'update', 'delete', and 'query' - - Raises: - MissingRequiredParameters: Raised if neither an id_ url_string nor an - entry are provided in the request. - - Returns: - The added entry. - """ - if entry is None and id_url_string is None: - raise MissingRequiredParameters('supply either an entry or URL string') - if entry is None and id_url_string is not None: - entry = BatchEntry(atom_id=atom.Id(text=id_url_string)) - # TODO: handle cases in which the entry lacks batch_... members. - #if not isinstance(entry, BatchEntry): - # Convert the entry to a batch entry. - if batch_id_string is not None: - entry.batch_id = BatchId(text=batch_id_string) - elif entry.batch_id is None or entry.batch_id.text is None: - entry.batch_id = BatchId(text=str(len(self.entry))) - if operation_string is not None: - entry.batch_operation = BatchOperation(op_type=operation_string) - self.entry.append(entry) - return entry - - def AddInsert(self, entry, batch_id_string=None): - """Add an insert request to the operations in this batch request feed. - - If the entry doesn't yet have an operation or a batch id, these will - be set to the insert operation and a batch_id specified as a parameter. - - Args: - entry: BatchEntry The entry which will be sent in the batch feed as an - insert request. - batch_id_string: str (optional) The batch ID to be used to reference - this batch operation in the results feed. If this parameter is None, - the current length of the feed's entry array will be used as a - count. Note that batch_ids should either always be specified or - never, mixing could potentially result in duplicate batch ids. - """ - entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string, - operation_string=BATCH_INSERT) - - def AddUpdate(self, entry, batch_id_string=None): - """Add an update request to the list of batch operations in this feed. - - Sets the operation type of the entry to insert if it is not already set - and assigns the desired batch id to the entry so that it can be - referenced in the server's response. - - Args: - entry: BatchEntry The entry which will be sent to the server as an - update (HTTP PUT) request. The item must have a valid atom id - so that the server knows which entry to replace. - batch_id_string: str (optional) The batch ID to be used to reference - this batch operation in the results feed. If this parameter is None, - the current length of the feed's entry array will be used as a - count. See also comments for AddInsert. - """ - entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string, - operation_string=BATCH_UPDATE) - - def AddDelete(self, url_string=None, entry=None, batch_id_string=None): - """Adds a delete request to the batch request feed. - - This method takes either the url_string which is the atom id of the item - to be deleted, or the entry itself. The atom id of the entry must be - present so that the server knows which entry should be deleted. - - Args: - url_string: str (optional) The URL of the entry to be deleted. You can - find this URL in the text member of the atom id for an entry. - entry: BatchEntry (optional) The entry to be deleted. - batch_id_string: str (optional) - - Raises: - MissingRequiredParameters: Raised if neither a url_string nor an entry - are provided in the request. - """ - entry = self.AddBatchEntry(entry=entry, id_url_string=url_string, - batch_id_string=batch_id_string, - operation_string=BATCH_DELETE) - - def AddQuery(self, url_string=None, entry=None, batch_id_string=None): - """Adds a query request to the batch request feed. - - This method takes either the url_string which is the query URL - whose results will be added to the result feed. The query URL will - be encapsulated in a BatchEntry, and you may pass in the BatchEntry - with a query URL instead of sending a url_string. - - Args: - url_string: str (optional) - entry: BatchEntry (optional) - batch_id_string: str (optional) - - Raises: - MissingRequiredParameters - """ - entry = self.AddBatchEntry(entry=entry, id_url_string=url_string, - batch_id_string=batch_id_string, - operation_string=BATCH_QUERY) - - def GetBatchLink(self): - for link in self.link: - if link.rel == 'http://schemas.google.com/g/2005#batch': - return link - return None - - -def BatchFeedFromString(xml_string): - return atom.CreateClassFromXMLString(BatchFeed, xml_string) - - -class EntryLink(atom.AtomBase): - """The gd:entryLink element""" - - _tag = 'entryLink' - _namespace = GDATA_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - # The entry used to be an atom.Entry, now it is a GDataEntry. - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry) - _attributes['rel'] = 'rel' - _attributes['readOnly'] = 'read_only' - _attributes['href'] = 'href' - - def __init__(self, href=None, read_only=None, rel=None, - entry=None, extension_elements=None, - extension_attributes=None, text=None): - self.href = href - self.read_only = read_only - self.rel = rel - self.entry = entry - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def EntryLinkFromString(xml_string): - return atom.CreateClassFromXMLString(EntryLink, xml_string) - - -class FeedLink(atom.AtomBase): - """The gd:feedLink element""" - - _tag = 'feedLink' - _namespace = GDATA_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _children['{%s}feed' % atom.ATOM_NAMESPACE] = ('feed', GDataFeed) - _attributes['rel'] = 'rel' - _attributes['readOnly'] = 'read_only' - _attributes['countHint'] = 'count_hint' - _attributes['href'] = 'href' - - def __init__(self, count_hint=None, href=None, read_only=None, rel=None, - feed=None, extension_elements=None, extension_attributes=None, - text=None): - self.count_hint = count_hint - self.href = href - self.read_only = read_only - self.rel = rel - self.feed = feed - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def FeedLinkFromString(xml_string): - return atom.CreateClassFromXMLString(FeedLink, xml_string) diff --git a/src/gam/gdata/apps/__init__.py b/src/gam/gdata/apps/__init__.py deleted file mode 100644 index ebdf98ec..00000000 --- a/src/gam/gdata/apps/__init__.py +++ /dev/null @@ -1,526 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2007 SIOS Technology, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Contains objects used with Google Apps.""" - -__author__ = 'tmatsuo@sios.com (Takashi MATSUO)' - - -import atom -import gdata - - -# XML namespaces which are often used in Google Apps entity. -APPS_NAMESPACE = 'http://schemas.google.com/apps/2006' -APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s' - - -class EmailList(atom.AtomBase): - """The Google Apps EmailList element""" - - _tag = 'emailList' - _namespace = APPS_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['name'] = 'name' - - def __init__(self, name=None, extension_elements=None, - extension_attributes=None, text=None): - self.name = name - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - -def EmailListFromString(xml_string): - return atom.CreateClassFromXMLString(EmailList, xml_string) - - -class Who(atom.AtomBase): - """The Google Apps Who element""" - - _tag = 'who' - _namespace = gdata.GDATA_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['rel'] = 'rel' - _attributes['email'] = 'email' - - def __init__(self, rel=None, email=None, extension_elements=None, - extension_attributes=None, text=None): - self.rel = rel - self.email = email - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - -def WhoFromString(xml_string): - return atom.CreateClassFromXMLString(Who, xml_string) - - -class Login(atom.AtomBase): - """The Google Apps Login element""" - - _tag = 'login' - _namespace = APPS_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['userName'] = 'user_name' - _attributes['password'] = 'password' - _attributes['suspended'] = 'suspended' - _attributes['admin'] = 'admin' - _attributes['changePasswordAtNextLogin'] = 'change_password' - _attributes['agreedToTerms'] = 'agreed_to_terms' - _attributes['ipWhitelisted'] = 'ip_whitelisted' - _attributes['hashFunctionName'] = 'hash_function_name' - - def __init__(self, user_name=None, password=None, suspended=None, - ip_whitelisted=None, hash_function_name=None, - admin=None, change_password=None, agreed_to_terms=None, - extension_elements=None, extension_attributes=None, - text=None): - self.user_name = user_name - self.password = password - self.suspended = suspended - self.admin = admin - self.change_password = change_password - self.agreed_to_terms = agreed_to_terms - self.ip_whitelisted = ip_whitelisted - self.hash_function_name = hash_function_name - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def LoginFromString(xml_string): - return atom.CreateClassFromXMLString(Login, xml_string) - - -class Quota(atom.AtomBase): - """The Google Apps Quota element""" - - _tag = 'quota' - _namespace = APPS_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['limit'] = 'limit' - - def __init__(self, limit=None, extension_elements=None, - extension_attributes=None, text=None): - self.limit = limit - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def QuotaFromString(xml_string): - return atom.CreateClassFromXMLString(Quota, xml_string) - - -class Name(atom.AtomBase): - """The Google Apps Name element""" - - _tag = 'name' - _namespace = APPS_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['familyName'] = 'family_name' - _attributes['givenName'] = 'given_name' - - def __init__(self, family_name=None, given_name=None, - extension_elements=None, extension_attributes=None, text=None): - self.family_name = family_name - self.given_name = given_name - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def NameFromString(xml_string): - return atom.CreateClassFromXMLString(Name, xml_string) - - -class Nickname(atom.AtomBase): - """The Google Apps Nickname element""" - - _tag = 'nickname' - _namespace = APPS_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['name'] = 'name' - - def __init__(self, name=None, - extension_elements=None, extension_attributes=None, text=None): - self.name = name - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def NicknameFromString(xml_string): - return atom.CreateClassFromXMLString(Nickname, xml_string) - - -class NicknameEntry(gdata.GDataEntry): - """A Google Apps flavor of an Atom Entry for Nickname""" - - _tag = 'entry' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataEntry._children.copy() - _attributes = gdata.GDataEntry._attributes.copy() - _children['{%s}login' % APPS_NAMESPACE] = ('login', Login) - _children['{%s}nickname' % APPS_NAMESPACE] = ('nickname', Nickname) - - def __init__(self, author=None, category=None, content=None, - atom_id=None, link=None, published=None, - title=None, updated=None, - login=None, nickname=None, - extended_property=None, - extension_elements=None, extension_attributes=None, text=None): - - gdata.GDataEntry.__init__(self, author=author, category=category, - content=content, - atom_id=atom_id, link=link, published=published, - title=title, updated=updated) - self.login = login - self.nickname = nickname - self.extended_property = extended_property or [] - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def NicknameEntryFromString(xml_string): - return atom.CreateClassFromXMLString(NicknameEntry, xml_string) - - -class NicknameFeed(gdata.GDataFeed, gdata.LinkFinder): - """A Google Apps Nickname feed flavor of an Atom Feed""" - - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataFeed._children.copy() - _attributes = gdata.GDataFeed._attributes.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [NicknameEntry]) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, - entry=None, total_results=None, start_index=None, - items_per_page=None, extension_elements=None, - extension_attributes=None, text=None): - gdata.GDataFeed.__init__(self, author=author, category=category, - contributor=contributor, generator=generator, - icon=icon, atom_id=atom_id, link=link, - logo=logo, rights=rights, subtitle=subtitle, - title=title, updated=updated, entry=entry, - total_results=total_results, - start_index=start_index, - items_per_page=items_per_page, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def NicknameFeedFromString(xml_string): - return atom.CreateClassFromXMLString(NicknameFeed, xml_string) - - -class UserEntry(gdata.GDataEntry): - """A Google Apps flavor of an Atom Entry""" - - _tag = 'entry' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataEntry._children.copy() - _attributes = gdata.GDataEntry._attributes.copy() - _children['{%s}login' % APPS_NAMESPACE] = ('login', Login) - _children['{%s}name' % APPS_NAMESPACE] = ('name', Name) - _children['{%s}quota' % APPS_NAMESPACE] = ('quota', Quota) - # This child may already be defined in GDataEntry, confirm before removing. - _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', - [gdata.FeedLink]) - _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who) - - def __init__(self, author=None, category=None, content=None, - atom_id=None, link=None, published=None, - title=None, updated=None, - login=None, name=None, quota=None, who=None, feed_link=None, - extended_property=None, - extension_elements=None, extension_attributes=None, text=None): - - gdata.GDataEntry.__init__(self, author=author, category=category, - content=content, - atom_id=atom_id, link=link, published=published, - title=title, updated=updated) - self.login = login - self.name = name - self.quota = quota - self.who = who - self.feed_link = feed_link or [] - self.extended_property = extended_property or [] - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def UserEntryFromString(xml_string): - return atom.CreateClassFromXMLString(UserEntry, xml_string) - - -class UserFeed(gdata.GDataFeed, gdata.LinkFinder): - """A Google Apps User feed flavor of an Atom Feed""" - - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataFeed._children.copy() - _attributes = gdata.GDataFeed._attributes.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [UserEntry]) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, - entry=None, total_results=None, start_index=None, - items_per_page=None, extension_elements=None, - extension_attributes=None, text=None): - gdata.GDataFeed.__init__(self, author=author, category=category, - contributor=contributor, generator=generator, - icon=icon, atom_id=atom_id, link=link, - logo=logo, rights=rights, subtitle=subtitle, - title=title, updated=updated, entry=entry, - total_results=total_results, - start_index=start_index, - items_per_page=items_per_page, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def UserFeedFromString(xml_string): - return atom.CreateClassFromXMLString(UserFeed, xml_string) - - -class EmailListEntry(gdata.GDataEntry): - """A Google Apps EmailList flavor of an Atom Entry""" - - _tag = 'entry' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataEntry._children.copy() - _attributes = gdata.GDataEntry._attributes.copy() - _children['{%s}emailList' % APPS_NAMESPACE] = ('email_list', EmailList) - # Might be able to remove this _children entry. - _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link', - [gdata.FeedLink]) - - def __init__(self, author=None, category=None, content=None, - atom_id=None, link=None, published=None, - title=None, updated=None, - email_list=None, feed_link=None, - extended_property=None, - extension_elements=None, extension_attributes=None, text=None): - - gdata.GDataEntry.__init__(self, author=author, category=category, - content=content, - atom_id=atom_id, link=link, published=published, - title=title, updated=updated) - self.email_list = email_list - self.feed_link = feed_link or [] - self.extended_property = extended_property or [] - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def EmailListEntryFromString(xml_string): - return atom.CreateClassFromXMLString(EmailListEntry, xml_string) - - -class EmailListFeed(gdata.GDataFeed, gdata.LinkFinder): - """A Google Apps EmailList feed flavor of an Atom Feed""" - - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataFeed._children.copy() - _attributes = gdata.GDataFeed._attributes.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListEntry]) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, - entry=None, total_results=None, start_index=None, - items_per_page=None, extension_elements=None, - extension_attributes=None, text=None): - gdata.GDataFeed.__init__(self, author=author, category=category, - contributor=contributor, generator=generator, - icon=icon, atom_id=atom_id, link=link, - logo=logo, rights=rights, subtitle=subtitle, - title=title, updated=updated, entry=entry, - total_results=total_results, - start_index=start_index, - items_per_page=items_per_page, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def EmailListFeedFromString(xml_string): - return atom.CreateClassFromXMLString(EmailListFeed, xml_string) - - -class EmailListRecipientEntry(gdata.GDataEntry): - """A Google Apps EmailListRecipient flavor of an Atom Entry""" - - _tag = 'entry' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataEntry._children.copy() - _attributes = gdata.GDataEntry._attributes.copy() - _children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who) - - def __init__(self, author=None, category=None, content=None, - atom_id=None, link=None, published=None, - title=None, updated=None, - who=None, - extended_property=None, - extension_elements=None, extension_attributes=None, text=None): - - gdata.GDataEntry.__init__(self, author=author, category=category, - content=content, - atom_id=atom_id, link=link, published=published, - title=title, updated=updated) - self.who = who - self.extended_property = extended_property or [] - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def EmailListRecipientEntryFromString(xml_string): - return atom.CreateClassFromXMLString(EmailListRecipientEntry, xml_string) - - -class EmailListRecipientFeed(gdata.GDataFeed, gdata.LinkFinder): - """A Google Apps EmailListRecipient feed flavor of an Atom Feed""" - - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataFeed._children.copy() - _attributes = gdata.GDataFeed._attributes.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', - [EmailListRecipientEntry]) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, - entry=None, total_results=None, start_index=None, - items_per_page=None, extension_elements=None, - extension_attributes=None, text=None): - gdata.GDataFeed.__init__(self, author=author, category=category, - contributor=contributor, generator=generator, - icon=icon, atom_id=atom_id, link=link, - logo=logo, rights=rights, subtitle=subtitle, - title=title, updated=updated, entry=entry, - total_results=total_results, - start_index=start_index, - items_per_page=items_per_page, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - - -def EmailListRecipientFeedFromString(xml_string): - return atom.CreateClassFromXMLString(EmailListRecipientFeed, xml_string) - - -class Property(atom.AtomBase): - """The Google Apps Property element""" - - _tag = 'property' - _namespace = APPS_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - _attributes['name'] = 'name' - _attributes['value'] = 'value' - - def __init__(self, name=None, value=None, extension_elements=None, - extension_attributes=None, text=None): - self.name = name - self.value = value - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def PropertyFromString(xml_string): - return atom.CreateClassFromXMLString(Property, xml_string) - - -class PropertyEntry(gdata.GDataEntry): - """A Google Apps Property flavor of an Atom Entry""" - - _tag = 'entry' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataEntry._children.copy() - _attributes = gdata.GDataEntry._attributes.copy() - _children['{%s}property' % APPS_NAMESPACE] = ('property', [Property]) - - def __init__(self, author=None, category=None, content=None, - atom_id=None, link=None, published=None, - title=None, updated=None, - property=None, - extended_property=None, - extension_elements=None, extension_attributes=None, text=None): - - gdata.GDataEntry.__init__(self, author=author, category=category, - content=content, - atom_id=atom_id, link=link, published=published, - title=title, updated=updated) - self.property = property - self.extended_property = extended_property or [] - self.text = text - self.extension_elements = extension_elements or [] - self.extension_attributes = extension_attributes or {} - - -def PropertyEntryFromString(xml_string): - return atom.CreateClassFromXMLString(PropertyEntry, xml_string) - -class PropertyFeed(gdata.GDataFeed, gdata.LinkFinder): - """A Google Apps Property feed flavor of an Atom Feed""" - - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.GDataFeed._children.copy() - _attributes = gdata.GDataFeed._attributes.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PropertyEntry]) - - def __init__(self, author=None, category=None, contributor=None, - generator=None, icon=None, atom_id=None, link=None, logo=None, - rights=None, subtitle=None, title=None, updated=None, - entry=None, total_results=None, start_index=None, - items_per_page=None, extension_elements=None, - extension_attributes=None, text=None): - gdata.GDataFeed.__init__(self, author=author, category=category, - contributor=contributor, generator=generator, - icon=icon, atom_id=atom_id, link=link, - logo=logo, rights=rights, subtitle=subtitle, - title=title, updated=updated, entry=entry, - total_results=total_results, - start_index=start_index, - items_per_page=items_per_page, - extension_elements=extension_elements, - extension_attributes=extension_attributes, - text=text) - -def PropertyFeedFromString(xml_string): - return atom.CreateClassFromXMLString(PropertyFeed, xml_string) diff --git a/src/gam/gdata/apps/audit/__init__.py b/src/gam/gdata/apps/audit/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/src/gam/gdata/apps/audit/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/gam/gdata/apps/audit/service.py b/src/gam/gdata/apps/audit/service.py deleted file mode 100644 index 66a5ae95..00000000 --- a/src/gam/gdata/apps/audit/service.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2008 Google, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Allow Google Apps domain administrators to audit user data. - - AuditService: Set auditing.""" - -__author__ = 'jlee@pbu.edu' - -from base64 import b64encode - -import gdata.apps -import gdata.apps.service -import gdata.service - -class AuditService(gdata.apps.service.PropertyService): - """Client for the Google Apps Audit service.""" - - def _serviceUrl(self, setting_id, domain=None, user=None): - if domain is None: - domain = self.domain - if user is None: - return '/a/feeds/compliance/audit/%s/%s' % (setting_id, domain) - else: - return '/a/feeds/compliance/audit/%s/%s/%s' % (setting_id, domain, user) - - def updatePGPKey(self, pgpkey): - """Updates Public PGP Key Google uses to encrypt audit data - - Args: - pgpkey: string, ASCII text of PGP Public Key to be used - - Returns: - A dict containing the result of the POST operation.""" - - uri = self._serviceUrl('publickey') - b64pgpkey = b64encode(pgpkey) - properties = {} - properties['publicKey'] = b64pgpkey - return self._PostProperties(uri, properties) - - def createEmailMonitor(self, source_user, destination_user, end_date, - begin_date=None, incoming_headers_only=False, - outgoing_headers_only=False, drafts=False, - drafts_headers_only=False, chats=False, - chats_headers_only=False): - """Creates a email monitor, forwarding the source_users emails/chats - - Args: - source_user: string, the user whose email will be audited - destination_user: string, the user to receive the audited email - end_date: string, the date the audit will end in - "yyyy-MM-dd HH:mm" format, required - begin_date: string, the date the audit will start in - "yyyy-MM-dd HH:mm" format, leave blank to use current time - incoming_headers_only: boolean, whether to audit only the headers of - mail delivered to source user - outgoing_headers_only: boolean, whether to audit only the headers of - mail sent from the source user - drafts: boolean, whether to audit draft messages of the source user - drafts_headers_only: boolean, whether to audit only the headers of - mail drafts saved by the user - chats: boolean, whether to audit archived chats of the source user - chats_headers_only: boolean, whether to audit only the headers of - archived chats of the source user - - Returns: - A dict containing the result of the POST operation.""" - - uri = self._serviceUrl('mail/monitor', user=source_user) - properties = {} - properties['destUserName'] = destination_user - if begin_date is not None: - properties['beginDate'] = begin_date - properties['endDate'] = end_date - if incoming_headers_only: - properties['incomingEmailMonitorLevel'] = 'HEADER_ONLY' - else: - properties['incomingEmailMonitorLevel'] = 'FULL_MESSAGE' - if outgoing_headers_only: - properties['outgoingEmailMonitorLevel'] = 'HEADER_ONLY' - else: - properties['outgoingEmailMonitorLevel'] = 'FULL_MESSAGE' - if drafts: - if drafts_headers_only: - properties['draftMonitorLevel'] = 'HEADER_ONLY' - else: - properties['draftMonitorLevel'] = 'FULL_MESSAGE' - if chats: - if chats_headers_only: - properties['chatMonitorLevel'] = 'HEADER_ONLY' - else: - properties['chatMonitorLevel'] = 'FULL_MESSAGE' - return self._PostProperties(uri, properties) - - def getEmailMonitors(self, user): - """"Gets the email monitors for the given user - - Args: - user: string, the user to retrieve email monitors for - - Returns: - list results of the POST operation - - """ - uri = self._serviceUrl('mail/monitor', user=user) - return self._GetPropertiesList(uri) - - def deleteEmailMonitor(self, source_user, destination_user): - """Deletes the email monitor for the given user - - Args: - source_user: string, the user who is being monitored - destination_user: string, theuser who recieves the monitored emails - - Returns: - Nothing - """ - - uri = self._serviceUrl('mail/monitor', user=source_user+'/'+destination_user) - try: - return self._DeleteProperties(uri) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def createAccountInformationRequest(self, user): - """Creates a request for account auditing details - - Args: - user: string, the user to request account information for - - Returns: - A dict containing the result of the post operation.""" - - uri = self._serviceUrl('account', user=user) - properties = {} - #XML Body is left empty - try: - return self._PostProperties(uri, properties) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def getAccountInformationRequestStatus(self, user, request_id): - """Gets the status of an account auditing request - - Args: - user: string, the user whose account auditing details were requested - request_id: string, the request_id - - Returns: - A dict containing the result of the get operation.""" - - uri = self._serviceUrl('account', user=user+'/'+request_id) - try: - return self._GetProperties(uri) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def getAllAccountInformationRequestsStatus(self): - """Gets the status of all account auditing requests for the domain - - Args: - None - - Returns: - list results of the POST operation - """ - - uri = self._serviceUrl('account') - return self._GetPropertiesList(uri) - - - def deleteAccountInformationRequest(self, user, request_id): - """Deletes the request for account auditing information - - Args: - user: string, the user whose account auditing details were requested - request_id: string, the request_id - - Returns: - Nothing - """ - - uri = self._serviceUrl('account', user=user+'/'+request_id) - try: - return self._DeleteProperties(uri) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def createMailboxExportRequest(self, user, begin_date=None, end_date=None, include_deleted=False, search_query=None, headers_only=False): - """Creates a mailbox export request - - Args: - user: string, the user whose mailbox export is being requested - begin_date: string, date of earliest emails to export, optional, defaults to date of account creation - format is 'yyyy-MM-dd HH:mm' - end_date: string, date of latest emails to export, optional, defaults to current date - format is 'yyyy-MM-dd HH:mm' - include_deleted: boolean, whether to include deleted emails in export, mutually exclusive with search_query - search_query: string, gmail style search query, matched emails will be exported, mutually exclusive with include_deleted - - Returns: - A dict containing the result of the post operation.""" - - uri = self._serviceUrl('mail/export', user=user) - properties = {} - if begin_date is not None: - properties['beginDate'] = begin_date - if end_date is not None: - properties['endDate'] = end_date - if include_deleted is not None: - properties['includeDeleted'] = gdata.apps.service._bool2str(include_deleted) - if search_query is not None: - properties['searchQuery'] = search_query - if headers_only is True: - properties['packageContent'] = 'HEADER_ONLY' - else: - properties['packageContent'] = 'FULL_MESSAGE' - return self._PostProperties(uri, properties) - - def getMailboxExportRequestStatus(self, user, request_id): - """Gets the status of an mailbox export request - - Args: - user: string, the user whose mailbox were requested - request_id: string, the request_id - - Returns: - A dict containing the result of the get operation.""" - - uri = self._serviceUrl('mail/export', user=user+'/'+request_id) - try: - return self._GetProperties(uri) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def getAllMailboxExportRequestsStatus(self): - """Gets the status of all mailbox export requests for the domain - - Args: - None - - Returns: - list results of the POST operation - """ - - uri = self._serviceUrl('mail/export') - return self._GetPropertiesList(uri) - - - def deleteMailboxExportRequest(self, user, request_id): - """Deletes the request for mailbox export - - Args: - user: string, the user whose mailbox were requested - request_id: string, the request_id - - Returns: - Nothing - """ - - uri = self._serviceUrl('mail/export', user=user+'/'+request_id) - try: - return self._DeleteProperties(uri) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) diff --git a/src/gam/gdata/apps/contacts/__init__.py b/src/gam/gdata/apps/contacts/__init__.py deleted file mode 100644 index 3787dfe2..00000000 --- a/src/gam/gdata/apps/contacts/__init__.py +++ /dev/null @@ -1,874 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Data model classes for parsing and generating XML for the Contacts API.""" - -import atom -import gdata - -## Constants from http://code.google.com/apis/gdata/elements.html ## -REL_HOME = 'http://schemas.google.com/g/2005#home' -REL_WORK = 'http://schemas.google.com/g/2005#work' -REL_OTHER = 'http://schemas.google.com/g/2005#other' - -IM_AIM = 'http://schemas.google.com/g/2005#AIM' # AOL Instant Messenger protocol -IM_MSN = 'http://schemas.google.com/g/2005#MSN' # MSN Messenger protocol -IM_YAHOO = 'http://schemas.google.com/g/2005#YAHOO' # Yahoo Messenger protocol -IM_SKYPE = 'http://schemas.google.com/g/2005#SKYPE' # Skype protocol -IM_QQ = 'http://schemas.google.com/g/2005#QQ' # QQ protocol -IM_GOOGLE_TALK = 'http://schemas.google.com/g/2005#GOOGLE_TALK' # Google Talk protocol -IM_ICQ = 'http://schemas.google.com/g/2005#ICQ' # ICQ protocol -IM_JABBER = 'http://schemas.google.com/g/2005#JABBER' # Jabber protocol -IM_NETMEETING = 'http://schemas.google.com/g/2005#NETMEETING' # NetMeeting - -PHOTO_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#photo' -PHOTO_EDIT_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#edit-photo' - -# Different phone types, for more info see: -# http://code.google.com/apis/gdata/docs/2.0/elements.html#gdPhoneNumber -PHONE_ASSISTANT = 'http://schemas.google.com/g/2005#assistant' -PHONE_CALLBACK = 'http://schemas.google.com/g/2005#callback' -PHONE_CAR = 'http://schemas.google.com/g/2005#car' -PHONE_COMPANY_MAIN = 'http://schemas.google.com/g/2005#company_main' -PHONE_FAX = 'http://schemas.google.com/g/2005#fax' -PHONE_GENERAL = 'http://schemas.google.com/g/2005#general' -PHONE_HOME = REL_HOME -PHONE_HOME_FAX = 'http://schemas.google.com/g/2005#home_fax' -PHONE_INTERNAL = 'http://schemas.google.com/g/2005#internal-extension' -PHONE_ISDN = 'http://schemas.google.com/g/2005#isdn' -PHONE_MAIN = 'http://schemas.google.com/g/2005#main' -PHONE_MOBILE = 'http://schemas.google.com/g/2005#mobile' -PHONE_OTHER = REL_OTHER -PHONE_OTHER_FAX = 'http://schemas.google.com/g/2005#other_fax' -PHONE_PAGER = 'http://schemas.google.com/g/2005#pager' -PHONE_RADIO = 'http://schemas.google.com/g/2005#radio' -PHONE_SATELLITE = 'http://schemas.google.com/g/2005#satellite' -PHONE_TELEX = 'http://schemas.google.com/g/2005#telex' -PHONE_TTY_TDD = 'http://schemas.google.com/g/2005#tty_tdd' -PHONE_VOIP = 'http://schemas.google.com/g/2005#voip' -PHONE_WORK = REL_WORK -PHONE_WORK_FAX = 'http://schemas.google.com/g/2005#work_fax' -PHONE_WORK_MOBILE = 'http://schemas.google.com/g/2005#work_mobile' -PHONE_WORK_PAGER = 'http://schemas.google.com/g/2005#work_pager' - -MAIL_BOTH = 'http://schemas.google.com/g/2005#both' -MAIL_LETTERS = 'http://schemas.google.com/g/2005#letters' -MAIL_PARCELS = 'http://schemas.google.com/g/2005#parcels' -MAIL_NEITHER = 'http://schemas.google.com/g/2005#neither' - -GENERAL_ADDRESS = 'http://schemas.google.com/g/2005#general' -LOCAL_ADDRESS = 'http://schemas.google.com/g/2005#local' - -EXTERNAL_ID_ORGANIZATION = 'organization' - -RELATION_MANAGER = 'manager' - -CONTACTS_NAMESPACE = 'http://schemas.google.com/contact/2008' - - -class GDataBase(atom.AtomBase): - """The Google Contacts intermediate class from atom.AtomBase.""" - _namespace = gdata.GDATA_NAMESPACE - _children = atom.AtomBase._children.copy() - _attributes = atom.AtomBase._attributes.copy() - - def __init__(self, text=None): - atom.AtomBase.__init__(self, text=text) - -class ContactsBase(GDataBase): - """The Google Contacts intermediate class for Contacts namespace.""" - _namespace = CONTACTS_NAMESPACE - -class BillingInformation(ContactsBase): - """The gContact:billingInformation element.""" - _tag = 'billingInformation' - -class Birthday(ContactsBase): - """The gContact:birthday element.""" - _tag = 'birthday' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['when'] = 'when' - - def __init__(self, when=None): - ContactsBase.__init__(self) - self.when = when - -class CalendarLink(ContactsBase): - """The gContact:calendarLink element.""" - _tag = 'calendarLink' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['href'] = 'href' - _attributes['label'] = 'label' - _attributes['primary'] = 'primary' - _attributes['rel'] = 'rel' - - def __init__(self, href=None, label=None, primary='false', rel=None): - ContactsBase.__init__(self) - self.href = href - self.label = label - self.primary = primary - self.rel = rel - -class Content(atom.AtomBase): - """The Google Contacts Content element.""" - _tag = 'content' - _namespace = atom.ATOM_NAMESPACE - - def __init__(self, text=None): - atom.AtomBase.__init__(self, text=text) - -class DirectoryServer(ContactsBase): - """The gContact:directoryServer element.""" - _tag = 'directoryServer' - -class Email(GDataBase): - """The gd:email element.""" - _tag = 'email' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['address'] = 'address' - _attributes['primary'] = 'primary' - _attributes['rel'] = 'rel' - _attributes['label'] = 'label' - - def __init__(self, label=None, rel=None, address=None, primary='false'): - GDataBase.__init__(self) - self.label = label - self.rel = rel - self.address = address - self.primary = primary - -class When(GDataBase): - """The Google Contacts when element.""" - _tag = 'when' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['startTime'] = 'startTime' - _attributes['label'] = 'label' - - def __init__(self, startTime=None, label=None): - GDataBase.__init__(self) - self.startTime = startTime - self.label = label - -class Event(ContactsBase): - """The gContact:event element.""" - _tag = 'event' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - _children['{%s}when' % GDataBase._namespace] = ('when', When) - - def __init__(self, label=None, rel=None, when=None): - ContactsBase.__init__(self) - self.label = label - self.rel = rel - self.when = when - -def EventFromString(xml_string): - return atom.CreateClassFromXMLString(Event, xml_string) - -class ExternalId(ContactsBase): - """The gContact:externalId element.""" - _tag = 'externalId' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - _attributes['value'] = 'value' - - def __init__(self, label=None, rel=None, value=None): - ContactsBase.__init__(self) - self.label = label - self.rel = rel - self.value = value - -def ExternalIdFromString(xml_string): - return atom.CreateClassFromXMLString(ExternalId, xml_string) - -class Gender(ContactsBase): - """The gContact:gender element.""" - _tag = 'gender' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['value'] = 'value' - - def __init__(self, value=None): - ContactsBase.__init__(self) - self.value = value - -class Hobby(ContactsBase): - """The gContact:hobby element.""" - _tag = 'hobby' - -class IM(GDataBase): - """The gd:im element.""" - _tag = 'im' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['address'] = 'address' - _attributes['primary'] = 'primary' - _attributes['protocol'] = 'protocol' - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - - def __init__(self, primary='false', rel=None, address=None, protocol=None, label=None): - GDataBase.__init__(self) - self.protocol = protocol - self.address = address - self.primary = primary - self.rel = rel - self.label = label - -class Initials(ContactsBase): - """The gContact:initials element.""" - _tag = 'initials' - -class Jot(ContactsBase): - """The gContact:jot element.""" - _tag = 'jot' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['rel'] = 'rel' - - def __init__(self, rel=None, text=None): - ContactsBase.__init__(self, text=text) - self.rel = rel - -class Language(ContactsBase): - """The gContact:language element.""" - _tag = 'language' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['code'] = 'code' - _attributes['label'] = 'label' - - def __init__(self, code=None, label=None): - ContactsBase.__init__(self) - self.code = code - self.label = label - -class MaidenName(ContactsBase): - """The gContact:maidenName element.""" - _tag = 'maidenName' - -class Mileage(ContactsBase): - """The gContact:mileage element.""" - _tag = 'mileage' - -class NamePrefix(GDataBase): - """The gd:namePrefix element.""" - _tag = 'namePrefix' - -class GivenName(GDataBase): - """The gd:givenName element.""" - _tag = 'givenName' - -class AdditionalName(GDataBase): - """The gd:additionalName element.""" - _tag = 'additionalName' - -class FamilyName(GDataBase): - """The gd:familyName element.""" - _tag = 'familyName' - -class NameSuffix(GDataBase): - """The gd:nameSuffix element.""" - _tag = 'nameSuffix' - -class FullName(GDataBase): - """The gd:fullName element.""" - _tag = 'fullName' - -class Name(GDataBase): - """The gd:name element.""" - _tag = 'name' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _children['{%s}namePrefix' % GDataBase._namespace] = ('name_prefix', NamePrefix) - _children['{%s}givenName' % GDataBase._namespace] = ('given_name', GivenName) - _children['{%s}additionalName' % GDataBase._namespace] = ('additional_name', AdditionalName) - _children['{%s}familyName' % GDataBase._namespace] = ('family_name', FamilyName) - _children['{%s}nameSuffix' % GDataBase._namespace] = ('name_suffix', NameSuffix) - _children['{%s}fullName' % GDataBase._namespace] = ('full_name', FullName) - - def __init__(self, given_name=None, additional_name=None, family_name=None, - name_prefix=None, name_suffix=None, full_name=None,): - GDataBase.__init__(self) - self.given_name = given_name - self.additional_name = additional_name - self.family_name = family_name - self.name_prefix = name_prefix - self.name_suffix = name_suffix - self.full_name = full_name - -class Nickname(ContactsBase): - """The gContact:nickname element.""" - _tag = 'nickname' - -class Occupation(ContactsBase): - """The gContact:occupation element.""" - _tag = 'occupation' - -class PhoneNumber(GDataBase): - """The gd:phoneNumber element.""" - _tag = 'phoneNumber' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - _attributes['uri'] = 'uri' - _attributes['primary'] = 'primary' - - def __init__(self, label=None, rel=None, uri=None, primary='false', text=None): - GDataBase.__init__(self, text=text) - self.label = label - self.rel = rel - self.uri = uri - self.primary = primary - -class OrgName(GDataBase): - """The gd:orgName element.""" - _tag = 'orgName' - -class OrgTitle(GDataBase): - """The gd:orgTitle element.""" - _tag = 'orgTitle' - -class OrgSymbol(GDataBase): - """The gd:orgSymbol element.""" - _tag = 'orgSymbol' - -class OrgDepartment(GDataBase): - """The gd:orgDepartment element.""" - _tag = 'orgDepartment' - -class OrgJobDescription(GDataBase): - """The gd:orgJobDescription element.""" - _tag = 'orgJobDescription' - -class Where(GDataBase): - """The gd:where element.""" - _tag = 'where' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['valueString'] = 'value_string' - - def __init__(self, value_string=None): - GDataBase.__init__(self) - self.value_string = value_string - -class Organization(GDataBase): - """The gd:organization element.""" - _tag = 'organization' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - _attributes['primary'] = 'primary' - _children['{%s}orgName' % GDataBase._namespace] = ('name', OrgName) - _children['{%s}orgSymbol' % GDataBase._namespace] = ('symbol', OrgSymbol) - _children['{%s}orgTitle' % GDataBase._namespace] = ('title', OrgTitle) - _children['{%s}orgDepartment' % GDataBase._namespace] = ('department', OrgDepartment) - _children['{%s}orgJobDescription' % GDataBase._namespace] = ('job_description', OrgJobDescription) - _children['{%s}where' % GDataBase._namespace] = ('where', Where) - - def __init__(self, label=None, rel=None, primary='false', name=None, - title=None, symbol=None, department=None, job_description=None, where=None,): - GDataBase.__init__(self) - self.label = label - self.rel = rel - self.primary = primary - self.name = name - self.symbol = symbol - self.title = title - self.department = department - self.job_description = job_description - self.where = where - -class PostalAddress(GDataBase): - """The gd:postalAddress element.""" - _tag = 'postalAddress' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - _attributes['primary'] = 'primary' - - def __init__(self, primary=None, rel=None, label=None, text=None): - GDataBase.__init__(self, text=text) - self.label = label - self.rel = rel - self.primary = primary - -class Priority(ContactsBase): - """The gContact:priority element.""" - _tag = 'priority' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['rel'] = 'rel' - - def __init__(self, rel=None): - ContactsBase.__init__(self) - self.rel = rel - -class Relation(ContactsBase): - """The gContact:relation element.""" - _tag = 'relation' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - - def __init__(self, label=None, rel=None, text=None): - ContactsBase.__init__(self, text=text) - self.label = label - self.rel = rel - -def RelationFromString(xml_string): - return atom.CreateClassFromXMLString(Relation, xml_string) - -class Sensitivity(ContactsBase): - """The gContact:sensitivity element.""" - _tag = 'sensitivity' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['rel'] = 'rel' - - def __init__(self, rel=None): - ContactsBase.__init__(self) - self.rel = rel - -class ShortName(ContactsBase): - """The gContact:shortName element.""" - _tag = 'shortName' - -class Street(GDataBase): - """The gd:street element. - Can be street, avenue, road, etc. This element also includes the - house number and room/apartment/flat/floor number. - """ - _tag = 'street' - -class PoBox(GDataBase): - """The gd:pobox element. - Covers actual P.O. boxes, drawers, locked bags, etc. This is usually - but not always mutually exclusive with street. - """ - _tag = 'pobox' - -class Neighborhood(GDataBase): - """The gd:neighborhood element. - This is used to disambiguate a street address when a city contains more - than one street with the same name, or to specify a small place whose - mail is routed through a larger postal town. In China it could be a - county or a minor city. - """ - _tag = 'neighborhood' - -class City(GDataBase): - """The gd:city element. - Can be city, village, town, borough, etc. This is the postal town and - not necessarily the place of residence or place of business. - """ - _tag = 'city' - -class Region(GDataBase): - """The gd:region element. - A state, province, county (in Ireland), Land (in Germany), - departement (in France), etc. - """ - _tag = 'region' - -class Postcode(GDataBase): - """The gd:postcode element. - Postal code. Usually country-wide, but sometimes specific to the - city (e.g. "2" in "Dublin 2, Ireland" addresses). - """ - _tag = 'postcode' - -class Country(GDataBase): - """The gd:country element. - The name or code of the country. - """ - _tag = 'country' - -class FormattedAddress(GDataBase): - """The gd:formattedAddress element.""" - _tag = 'formattedAddress' - -class StructuredPostalAddress(GDataBase): - """The gd:structuredPostalAddress element.""" - _tag = 'structuredPostalAddress' - _children = GDataBase._children.copy() - _attributes = GDataBase._attributes.copy() - _attributes['label'] = 'label' - _attributes['rel'] = 'rel' - _attributes['primary'] = 'primary' - _children['{%s}street' % GDataBase._namespace] = ('street', Street) - _children['{%s}pobox' % GDataBase._namespace] = ('pobox', PoBox) - _children['{%s}neighborhood' % GDataBase._namespace] = ('neighborhood', Neighborhood) - _children['{%s}city' % GDataBase._namespace] = ('city', City) - _children['{%s}region' % GDataBase._namespace] = ('region', Region) - _children['{%s}postcode' % GDataBase._namespace] = ('postcode', Postcode) - _children['{%s}country' % GDataBase._namespace] = ('country', Country) - _children['{%s}formattedAddress' % GDataBase._namespace] = ('formatted_address', FormattedAddress) - - def __init__(self, rel=None, label=None, primary='false', - street=None, - pobox=None, - neighborhood=None, - city=None, - region=None, - postcode=None, - country=None, - formatted_address=None): - GDataBase.__init__(self) - self.label = label - self.rel = rel - self.primary = primary - self.street = street - self.pobox = pobox - self.neighborhood = neighborhood - self.city = city - self.region = region - self.postcode = postcode - self.country = country - self.formatted_address = formatted_address - -class Subject(ContactsBase): - """The gContact:Subject element.""" - _tag = 'subject' - -class UserDefinedField(ContactsBase): - """The gContact:userDefinedField element.""" - _tag = 'userDefinedField' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['key'] = 'key' - _attributes['value'] = 'value' - - def __init__(self, key=None, value=None): - ContactsBase.__init__(self) - self.key = key - self.value = value - -def UserDefinedFieldFromString(xml_string): - return atom.CreateClassFromXMLString(UserDefinedField, xml_string) - -class Website(ContactsBase): - """The gContact:Website element.""" - _tag = 'website' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['href'] = 'href' - _attributes['label'] = 'label' - _attributes['primary'] = 'primary' - _attributes['rel'] = 'rel' - - def __init__(self, href=None, label=None, primary='false', rel=None): - ContactsBase.__init__(self) - self.href = href - self.label = label - self.primary = primary - self.rel = rel - -def WebsiteFromString(xml_string): - return atom.CreateClassFromXMLString(Website, xml_string) - -class PersonEntry(gdata.BatchEntry): - """Base class for ContactEntry and ProfileEntry.""" - _children = gdata.BatchEntry._children.copy() - _children['{%s}billingInformation' % CONTACTS_NAMESPACE] = ('billingInformation', BillingInformation) - _children['{%s}birthday' % CONTACTS_NAMESPACE] = ('birthday', Birthday) - _children['{%s}calendarLink' % CONTACTS_NAMESPACE] = ('calendarLink', [CalendarLink]) - _children['{%s}content' % atom.ATOM_NAMESPACE] = ('content', Content) - _children['{%s}directoryServer' % CONTACTS_NAMESPACE] = ('directoryServer', DirectoryServer) - _children['{%s}email' % gdata.GDATA_NAMESPACE] = ('email', [Email]) - _children['{%s}event' % CONTACTS_NAMESPACE] = ('event', [Event]) - _children['{%s}externalId' % CONTACTS_NAMESPACE] = ('externalId', [ExternalId]) - _children['{%s}gender' % CONTACTS_NAMESPACE] = ('gender', Gender) - _children['{%s}hobby' % CONTACTS_NAMESPACE] = ('hobby', [Hobby]) - _children['{%s}im' % gdata.GDATA_NAMESPACE] = ('im', [IM]) - _children['{%s}initials' % CONTACTS_NAMESPACE] = ('initials', Initials) - _children['{%s}jot' % CONTACTS_NAMESPACE] = ('jot', [Jot]) - _children['{%s}language' % CONTACTS_NAMESPACE] = ('language', Language) - _children['{%s}maidenName' % CONTACTS_NAMESPACE] = ('maidenName', MaidenName) - _children['{%s}mileage' % CONTACTS_NAMESPACE] = ('mileage', Mileage) - _children['{%s}name' % gdata.GDATA_NAMESPACE] = ('name', Name) - _children['{%s}nickname' % CONTACTS_NAMESPACE] = ('nickname', Nickname) - _children['{%s}occupation' % CONTACTS_NAMESPACE] = ('occupation', Occupation) - _children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', [Organization]) - _children['{%s}phoneNumber' % gdata.GDATA_NAMESPACE] = ('phoneNumber', [PhoneNumber]) - _children['{%s}postalAddress' % gdata.GDATA_NAMESPACE] = ('postalAddress', [PostalAddress]) - _children['{%s}priority' % CONTACTS_NAMESPACE] = ('priority', Priority) - _children['{%s}relation' % CONTACTS_NAMESPACE] = ('relation', [Relation]) - _children['{%s}sensitivity' % CONTACTS_NAMESPACE] = ('sensitivity', Sensitivity) - _children['{%s}shortName' % CONTACTS_NAMESPACE] = ('shortName', ShortName) - _children['{%s}structuredPostalAddress' % gdata.GDATA_NAMESPACE] = ('structuredPostalAddress', [StructuredPostalAddress]) - _children['{%s}subject' % CONTACTS_NAMESPACE] = ('subject', Subject) - _children['{%s}userDefinedField' % CONTACTS_NAMESPACE] = ('userDefinedField', [UserDefinedField]) - _children['{%s}website' % CONTACTS_NAMESPACE] = ('website', [Website]) - _children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', Where) - - _attributes = gdata.BatchEntry._attributes.copy() - _attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag' - - def __init__(self, - billingInformation=None, - birthday=None, - calendarLink=None, - content=None, - directoryServer=None, - email=None, - event=None, - externalId=None, - gender=None, - hobby=None, - im=None, - initials=None, - jot=None, - language=None, - maidenName=None, - mileage=None, - name=None, - nickname=None, - occupation=None, - organization=None, - phoneNumber=None, - postalAddress=None, - priority=None, - relation=None, - sensitivity=None, - shortName=None, - structuredPostalAddress=None, - subject=None, - text=None, - title=None, - userDefinedField=None, - website=None, - where=None, - etag=None): - gdata.BatchEntry.__init__(self) - self.billingInformation = billingInformation - self.birthday = birthday - self.calendarLink = calendarLink or [] - self.content = content - self.directoryServer = directoryServer - self.email = email or [] - self.event = event or [] - self.externalId = externalId or [] - self.gender = gender - self.hobby = hobby or [] - self.im = im or [] - self.initials = initials - self.jot = jot or [] - self.language = language - self.maidenName = maidenName - self.mileage = mileage - self.name = name - self.nickname = nickname - self.occupation = occupation - self.organization = organization or [] - self.phoneNumber = phoneNumber or [] - self.postalAddress = postalAddress or [] - self.priority = priority - self.relation = relation or [] - self.sensitivity = sensitivity - self.shortName = shortName - self.structuredPostalAddress = structuredPostalAddress or [] - self.subject = subject - self.text = text - self.userDefinedField = userDefinedField or [] - self.website = website or [] - self.where = where - self.extension_attributes = {} - self.extension_elements = [] - self.etag = etag - -class Deleted(GDataBase): - """The gd:Deleted element.""" - _tag = 'deleted' - -class GroupMembershipInfo(ContactsBase): - """The Google Contacts GroupMembershipInfo element.""" - _tag = 'groupMembershipInfo' - _children = ContactsBase._children.copy() - _attributes = ContactsBase._attributes.copy() - _attributes['deleted'] = 'deleted' - _attributes['href'] = 'href' - - def __init__(self, deleted=None, href=None, text=None): - ContactsBase.__init__(self, text=text) - self.deleted = deleted - self.href = href - -class ContactEntry(PersonEntry): - """Represents a contact.""" - _tag = 'entry' - _namespace = atom.ATOM_NAMESPACE - _children = PersonEntry._children.copy() - - _children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted) - _children['{%s}groupMembershipInfo' % CONTACTS_NAMESPACE] = ('groupMembershipInfo', [GroupMembershipInfo]) - _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ('extended_property', [gdata.ExtendedProperty]) - # Overwrite the organization rule in PersonEntry so that a ContactEntry - # may only contain one element. - #_children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', Organization) - - def __init__(self, - billingInformation=None, - birthday=None, - calendarLink=None, - content=None, - deleted=None, - directoryServer=None, - email=None, - event=None, - extended_property=None, - externalId=None, - gender=None, - groupMembershipInfo=None, - hobby=None, - im=None, - initials=None, - jot=None, - language=None, - maidenName=None, - mileage=None, - name=None, - nickname=None, - occupation=None, - organization=None, - phoneNumber=None, - postalAddress=None, - priority=None, - relation=None, - sensitivity=None, - shortName=None, - structuredPostalAddress=None, - subject=None, - text=None, - title=None, - userDefinedField=None, - website=None, - where=None, - etag=None): - PersonEntry.__init__(self, - billingInformation=billingInformation, - birthday=birthday, - calendarLink=calendarLink, - content=content, - directoryServer=directoryServer, - email=email, - event=event, - externalId=externalId, - gender=gender, - hobby=hobby, - im=im, - initials=initials, - jot=jot, - language=language, - maidenName=maidenName, - mileage=mileage, - name=name, - nickname=nickname, - occupation=occupation, - organization=organization, - phoneNumber=phoneNumber, - postalAddress=postalAddress, - priority=priority, - relation=relation, - sensitivity=sensitivity, - shortName=shortName, - structuredPostalAddress=structuredPostalAddress, - subject=subject, - text=text, - title=title, - userDefinedField=userDefinedField, - website=website, - where=where, - etag=etag) - self.deleted = deleted - self.extended_property = extended_property or [] - self.groupMembershipInfo = groupMembershipInfo or [] - - def GetPhotoLink(self): - for a_link in self.link: - if a_link.rel == PHOTO_LINK_REL: - return a_link - return None - - def GetPhotoEditLink(self): - for a_link in self.link: - if a_link.rel == PHOTO_EDIT_LINK_REL: - return a_link - return None - -def ContactEntryFromString(xml_string): - return atom.CreateClassFromXMLString(ContactEntry, xml_string) - -class ContactsFeed(gdata.BatchFeed, gdata.LinkFinder): - """A Google contacts feed flavor of an Atom Feed.""" - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.BatchFeed._children.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ContactEntry]) - - def __init__(self): - gdata.BatchFeed.__init__(self) - -def ContactsFeedFromString(xml_string): - return atom.CreateClassFromXMLString(ContactsFeed, xml_string) - -class GroupEntry(gdata.BatchEntry): - """Represents a contact group.""" - _children = gdata.BatchEntry._children.copy() - _children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted) - _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ('extended_property', [gdata.ExtendedProperty]) - - _attributes = gdata.BatchEntry._attributes.copy() - _attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag' - - def __init__(self, - title=None, - deleted=None, - extended_property=None, - etag=None): - gdata.BatchEntry.__init__(self) - self.title = title - self.deleted = deleted - self.extended_property = extended_property or [] - self.etag = etag - -def GroupEntryFromString(xml_string): - return atom.CreateClassFromXMLString(GroupEntry, xml_string) - -class GroupsFeed(gdata.BatchFeed): - """A Google contact groups feed flavor of an Atom Feed.""" - _tag = 'feed' - _namespace = atom.ATOM_NAMESPACE - _children = gdata.BatchFeed._children.copy() - _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GroupEntry]) - - def __init__(self): - gdata.BatchFeed.__init__(self) - -def GroupsFeedFromString(xml_string): - return atom.CreateClassFromXMLString(GroupsFeed, xml_string) diff --git a/src/gam/gdata/apps/contacts/service.py b/src/gam/gdata/apps/contacts/service.py deleted file mode 100644 index e525426f..00000000 --- a/src/gam/gdata/apps/contacts/service.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Google Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""ContactsService extends the GDataService for Google Contacts operations. - - ContactsService: Provides methods to query feeds and manipulate items. - Extends GDataService. -""" - - -import gdata.apps -import gdata.apps.service -import gdata.service - - -class ContactsService(gdata.service.GDataService): - """Client for the Google Contacts service.""" - - def __init__(self, email=None, password=None, source=None, - server='www.google.com', additional_headers=None, - contact_list='default', contactFeed=True, **kwargs): - """Creates a client for the Contacts service. - - Args: - email: string (optional) The user's email address, used for - authentication. - password: string (optional) The user's password. - source: string (optional) The name of the user's application. - server: string (optional) The name of the server to which a connection - will be opened. Default value: 'www.google.com'. - contact_list: string (optional) The name of the default contact list to - use when no URI is specified to the methods of the service. - Default value: 'default' (the logged in user's contact list). - contactFeed: Boolean (optional) Is this contacts feed or a gal feed - Default value: True (the logged in user's contact list). - **kwargs: The other parameters to pass to gdata.service.GDataService - constructor. - """ - - self.contact_list = contact_list - self.feed_type = ['gal', 'contacts'][contactFeed] - if additional_headers == None: - additional_headers = {} - additional_headers['GData-Version'] = ['1.1', '3.1'][contactFeed] - gdata.service.GDataService.__init__(self, - email=email, password=password, service='cp', source=source, - server=server, additional_headers=additional_headers, **kwargs) - self.ssl = True - self.port = 443 - - def _CleanUri(self, uri): - """Sanitizes a feed URI. - - Args: - uri: The URI to sanitize, can be relative or absolute. - - Returns: - The given URI without its https://server prefix, if any. - Keeps the leading slash of the URI. - """ - url_prefix = 'https://%s' % self.server - if uri.startswith(url_prefix): - uri = uri[len(url_prefix):] - return uri - - def GetContactFeedUri(self, contact_list=None, projection='full', contactId=None): - """Builds a contact feed URI. """ - contact_list = contact_list or self.contact_list - uri = 'https://{0}/m8/feeds/{1}/{2}/{3}'.format(self.server, self.feed_type, contact_list, projection) - if contactId: - uri += '/{0}'.format(contactId) - return uri - - def GetContactsFeed(self, uri=None, - extra_headers=None, url_params=None, escape_params=True): - uri = uri or self.GetContactFeedUri() - try: - return self.Get(uri, - url_params=url_params, extra_headers=extra_headers, escape_params=escape_params, - converter=gdata.apps.contacts.ContactsFeedFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def GetContact(self, uri): - try: - return self.Get(uri, converter=gdata.apps.contacts.ContactEntryFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def CreateContact(self, new_contact, insert_uri=None, url_params=None, - escape_params=True): - """Adds an new contact to Google Contacts. - - Args: - new_contact: atom.Entry or subclass A new contact which is to be added to - Google Contacts. - insert_uri: the URL to post new contacts to the feed - url_params: dict (optional) Additional URL parameters to be included - in the insertion request. - escape_params: boolean (optional) If true, the url_parameters will be - escaped before they are included in the request. - - Returns: - On successful insert, an entry containing the contact created - On failure, a RequestError is raised of the form: - {'status': HTTP status code from server, - 'reason': HTTP reason from the server, - 'body': HTTP body of the server's response} - """ - insert_uri = insert_uri or self.GetContactFeedUri() - try: - return self.Post(new_contact, insert_uri, url_params=url_params, - escape_params=escape_params, - converter=gdata.apps.contacts.ContactEntryFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def UpdateContact(self, edit_uri, updated_contact, extra_headers=None, url_params=None, - escape_params=True): - """Updates an existing contact. - - Args: - edit_uri: string The edit link URI for the element being updated - updated_contact: string, atom.Entry or subclass containing - the Atom Entry which will replace the contact which is - stored at the edit_url - url_params: dict (optional) Additional URL parameters to be included - in the update request. - escape_params: boolean (optional) If true, the url_parameters will be - escaped before they are included in the request. - - Returns: - On successful update, a httplib.HTTPResponse containing the server's - response to the PUT request. - On failure, a RequestError is raised of the form: - {'status': HTTP status code from server, - 'reason': HTTP reason from the server, - 'body': HTTP body of the server's response} - """ - try: - return self.Put(updated_contact, self._CleanUri(edit_uri), - url_params=url_params, extra_headers=extra_headers, - escape_params=escape_params, - converter=gdata.apps.contacts.ContactEntryFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def DeleteContact(self, edit_uri, extra_headers=None, - url_params=None, escape_params=True): - """Removes an contact with the specified ID from Google Contacts. - - Args: - edit_uri: string The edit URL of the entry to be deleted. Example: - '/m8/feeds/contacts/default/full/xxx/yyy' - extra_headers: dict (optional) - url_params: dict (optional) Additional URL parameters to be included - in the deletion request. - escape_params: boolean (optional) If true, the url_parameters will be - escaped before they are included in the request. - - Returns: - On successful delete, a httplib.HTTPResponse containing the server's - response to the DELETE request. - On failure, a RequestError is raised of the form: - {'status': HTTP status code from server, - 'reason': HTTP reason from the server, - 'body': HTTP body of the server's response} - """ - try: - return self.Delete(self._CleanUri(edit_uri), - url_params=url_params, escape_params=escape_params, extra_headers=extra_headers) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def ChangePhoto(self, media, contact_entry_or_url, content_type=None, - content_length=None, extra_headers=None): - """Change the photo for the contact by uploading a new photo. - - Performs a PUT against the photo edit URL to send the binary data for the - photo. - - Args: - media: filename, file-like-object, or a gdata.MediaSource object to send. - contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this - method will search for an edit photo link URL and - perform a PUT to the URL. - content_type: str (optional) the mime type for the photo data. This is - necessary if media is a file or file name, but if media - is a MediaSource object then the media object can contain - the mime type. If media_type is set, it will override the - mime type in the media object. - content_length: int or str (optional) Specifying the content length is - only required if media is a file-like object. If media - is a filename, the length is determined using - os.path.getsize. If media is a MediaSource object, it is - assumed that it already contains the content length. - extra_headers: dict (optional) - """ - if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry): -# url = contact_entry_or_url.GetPhotoEditLink().href - url = contact_entry_or_url.GetPhotoLink().href - else: - url = contact_entry_or_url - if isinstance(media, gdata.MediaSource): - payload = media - # If the media object is a file-like object, then use it as the file - # handle in the in the MediaSource. - elif hasattr(media, 'read'): - payload = gdata.MediaSource(file_handle=media, - content_type=content_type, content_length=content_length) - # Assume that the media object is a file name. - else: - payload = gdata.MediaSource(content_type=content_type, - content_length=content_length, file_path=media) - return self.Put(payload, url, extra_headers=extra_headers) - - def GetPhoto(self, contact_entry_or_url): - """Retrives the binary data for the contact's profile photo as a string. - - Args: - contact_entry_or_url: a gdata.apps.contacts.ContactEntry object or a string - containing the photo link's URL. If the contact entry does not - contain a photo link, the image will not be fetched and this method - will return None. - """ - # TODO: add the ability to write out the binary image data to a file, - # reading and writing a chunk at a time to avoid potentially using up - # large amounts of memory. - url = None - if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry): - photo_link = contact_entry_or_url.GetPhotoLink() - if photo_link: - url = photo_link.href - else: - url = contact_entry_or_url - if url: - try: - return self.Get(url, converter=str) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - else: - return None - - def DeletePhoto(self, contact_entry_or_url, extra_headers=None): - """Deletes the contact's profile photo. - - Args: - contact_entry_or_url: a gdata.apps.contacts.ContactEntry object or a string - containing the photo link's URL. - will return None. - extra_headers: dict (optional) - """ - url = None - if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry): -# url = contact_entry_or_url.GetPhotoEditLink().href - url = contact_entry_or_url.GetPhotoLink().href - else: - url = contact_entry_or_url - if url: - self.Delete(url, extra_headers=extra_headers) - - def ExecuteBatch(self, batch_feed, url, - converter=gdata.apps.contacts.ContactsFeedFromString): - """Sends a batch request feed to the server. - - Args: - batch_feed: gdata.apps.contacts.ContactFeed A feed containing batch - request entries. Each entry contains the operation to be performed - on the data contained in the entry. For example an entry with an - operation type of insert will be used as if the individual entry - had been inserted. - url: str The batch URL to which these operations should be applied. - converter: Function (optional) The function used to convert the server's - response to an object. The default value is ContactsFeedFromString. - - Returns: - The results of the batch request's execution on the server. If the - default converter is used, this is stored in a ContactsFeed. - """ - return self.Post(batch_feed, url, converter=converter) - - def GetContactGroupFeedUri(self, contact_list=None, projection='full', groupId=None): - """Builds a contact feed URI. """ - contact_list = contact_list or self.contact_list - uri = 'https://{0}/m8/feeds/groups/{1}/{2}'.format(self.server, contact_list, projection) - if groupId: - uri += '/{0}'.format(groupId) - return uri - - def GetGroupsFeed(self, uri=None, - extra_headers=None, url_params=None, escape_params=True): - uri = uri or self.GetContactGroupFeedUri() - try: - return self.Get(uri, - url_params=url_params, extra_headers=extra_headers, escape_params=escape_params, - converter=gdata.apps.contacts.GroupsFeedFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def GetGroup(self, uri): - try: - return self.Get(uri, converter=gdata.apps.contacts.GroupEntryFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def CreateGroup(self, new_group, insert_uri=None, url_params=None, - escape_params=True): - insert_uri = insert_uri or self.GetContactGroupFeedUri() - try: - return self.Post(new_group, insert_uri, url_params=url_params, - escape_params=escape_params, - converter=gdata.apps.contacts.GroupEntryFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def UpdateGroup(self, edit_uri, updated_group, extra_headers=None, url_params=None, - escape_params=True): - try: - return self.Put(updated_group, self._CleanUri(edit_uri), - url_params=url_params, extra_headers=extra_headers, - escape_params=escape_params, - converter=gdata.apps.contacts.GroupEntryFromString) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def DeleteGroup(self, edit_uri, extra_headers=None, - url_params=None, escape_params=True): - try: - return self.Delete(self._CleanUri(edit_uri), - url_params=url_params, escape_params=escape_params, extra_headers=extra_headers) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - -class ContactsQuery(gdata.service.Query): - - def __init__(self, feed=None, text_query=None, params=None, - categories=None, group=None): - self.feed = feed or '/m8/feeds/contacts/default/full' - if group: - self['group'] = group - gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query, - params=params, categories=categories) diff --git a/src/gam/gdata/apps/service.py b/src/gam/gdata/apps/service.py deleted file mode 100644 index c38dc8c4..00000000 --- a/src/gam/gdata/apps/service.py +++ /dev/null @@ -1,544 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2007 SIOS Technology, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__author__ = 'tmatsuo@sios.com (Takashi MATSUO)' - -import lxml.etree as ElementTree -import urllib.request, urllib.parse, urllib.error -import gdata -import atom.service -import gdata.service -import gdata.apps -import atom - -API_VER="2.0" -HTTP_OK=200 - -UNKOWN_ERROR=1000 -USER_DELETED_RECENTLY=1100 -USER_SUSPENDED=1101 -DOMAIN_USER_LIMIT_EXCEEDED=1200 -DOMAIN_ALIAS_LIMIT_EXCEEDED=1201 -DOMAIN_SUSPENDED=1202 -DOMAIN_FEATURE_UNAVAILABLE=1203 -ENTITY_EXISTS=1300 -ENTITY_DOES_NOT_EXIST=1301 -ENTITY_NAME_IS_RESERVED=1302 -ENTITY_NAME_NOT_VALID=1303 -INVALID_GIVEN_NAME=1400 -INVALID_FAMILY_NAME=1401 -INVALID_PASSWORD=1402 -INVALID_USERNAME=1403 -INVALID_HASH_FUNCTION_NAME=1404 -INVALID_HASH_DIGGEST_LENGTH=1405 -INVALID_EMAIL_ADDRESS=1406 -INVALID_QUERY_PARAMETER_VALUE=1407 -TOO_MANY_RECIPIENTS_ON_EMAIL_LIST=1500 - -DEFAULT_QUOTA_LIMIT='2048' - - -class Error(Exception): - pass - - -class AppsForYourDomainException(Error): - - def __init__(self, response): - - Error.__init__(self, response) - try: - self.element_tree = ElementTree.fromstring(response['body']) - self.error_code = int(self.element_tree[0].attrib['errorCode']) - self.reason = self.element_tree[0].attrib['reason'] - self.invalidInput = self.element_tree[0].attrib['invalidInput'] - except: - self.error_code = 600 - -class AppsService(gdata.service.GDataService): - """Client for the Google Apps Provisioning service.""" - - def __init__(self, email=None, password=None, domain=None, source=None, - server='apps-apis.google.com', additional_headers=None, - **kwargs): - """Creates a client for the Google Apps Provisioning service. - - Args: - email: string (optional) The user's email address, used for - authentication. - password: string (optional) The user's password. - domain: string (optional) The Google Apps domain name. - source: string (optional) The name of the user's application. - server: string (optional) The name of the server to which a connection - will be opened. Default value: 'apps-apis.google.com'. - **kwargs: The other parameters to pass to gdata.service.GDataService - constructor. - """ - gdata.service.GDataService.__init__( - self, email=email, password=password, service='apps', source=source, - server=server, additional_headers=additional_headers, **kwargs) - self.ssl = True - self.port = 443 - self.domain = domain - - def _baseURL(self): - return "/a/feeds/%s" % self.domain - - def AddAllElementsFromAllPages(self, link_finder, func): - """retrieve all pages and add all elements""" - next = link_finder.GetNextLink() - while next is not None: - next_feed = self.Get(next.href, converter=func) - for a_entry in next_feed.entry: - link_finder.entry.append(a_entry) - next = next_feed.GetNextLink() - return link_finder - - def RetrievePageOfEmailLists(self, start_email_list_name=None, - num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, - backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve one page of email list""" - uri = "%s/emailList/%s" % (self._baseURL(), API_VER) - if start_email_list_name is not None: - uri += "?startEmailListName=%s" % start_email_list_name - try: - return gdata.apps.EmailListFeedFromString(str(self.GetWithRetries( - uri, num_retries=num_retries, delay=delay, backoff=backoff))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def GetGeneratorForAllEmailLists( - self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve a generator for all emaillists in this domain.""" - first_page = self.RetrievePageOfEmailLists(num_retries=num_retries, - delay=delay, - backoff=backoff) - return self.GetGeneratorFromLinkFinder( - first_page, gdata.apps.EmailListRecipientFeedFromString, - num_retries=num_retries, delay=delay, backoff=backoff) - - def RetrieveAllEmailLists(self): - """Retrieve all email list of a domain.""" - - ret = self.RetrievePageOfEmailLists() - # pagination - return self.AddAllElementsFromAllPages( - ret, gdata.apps.EmailListFeedFromString) - - def RetrieveEmailList(self, list_name): - """Retreive a single email list by the list's name.""" - - uri = "%s/emailList/%s/%s" % ( - self._baseURL(), API_VER, list_name) - try: - return self.Get(uri, converter=gdata.apps.EmailListEntryFromString) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def RetrieveEmailLists(self, recipient): - """Retrieve All Email List Subscriptions for an Email Address.""" - - uri = "%s/emailList/%s?recipient=%s" % ( - self._baseURL(), API_VER, recipient) - try: - ret = gdata.apps.EmailListFeedFromString(str(self.Get(uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - # pagination - return self.AddAllElementsFromAllPages( - ret, gdata.apps.EmailListFeedFromString) - - def RemoveRecipientFromEmailList(self, recipient, list_name): - """Remove recipient from email list.""" - - uri = "%s/emailList/%s/%s/recipient/%s" % ( - self._baseURL(), API_VER, list_name, recipient) - try: - self.Delete(uri) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def RetrievePageOfRecipients(self, list_name, start_recipient=None, - num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, - backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve one page of recipient of an email list. """ - - uri = "%s/emailList/%s/%s/recipient" % ( - self._baseURL(), API_VER, list_name) - - if start_recipient is not None: - uri += "?startRecipient=%s" % start_recipient - try: - return gdata.apps.EmailListRecipientFeedFromString(str( - self.GetWithRetries( - uri, num_retries=num_retries, delay=delay, backoff=backoff))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def GetGeneratorForAllRecipients( - self, list_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve a generator for all recipients of a particular emaillist.""" - first_page = self.RetrievePageOfRecipients(list_name, - num_retries=num_retries, - delay=delay, - backoff=backoff) - return self.GetGeneratorFromLinkFinder( - first_page, gdata.apps.EmailListRecipientFeedFromString, - num_retries=num_retries, delay=delay, backoff=backoff) - - def RetrieveAllRecipients(self, list_name): - """Retrieve all recipient of an email list.""" - - ret = self.RetrievePageOfRecipients(list_name) - # pagination - return self.AddAllElementsFromAllPages( - ret, gdata.apps.EmailListRecipientFeedFromString) - - def AddRecipientToEmailList(self, recipient, list_name): - """Add a recipient to a email list.""" - - uri = "%s/emailList/%s/%s/recipient" % ( - self._baseURL(), API_VER, list_name) - recipient_entry = gdata.apps.EmailListRecipientEntry() - recipient_entry.who = gdata.apps.Who(email=recipient) - - try: - return gdata.apps.EmailListRecipientEntryFromString( - str(self.Post(recipient_entry, uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def DeleteEmailList(self, list_name): - """Delete a email list""" - - uri = "%s/emailList/%s/%s" % (self._baseURL(), API_VER, list_name) - try: - self.Delete(uri) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def CreateEmailList(self, list_name): - """Create a email list. """ - - uri = "%s/emailList/%s" % (self._baseURL(), API_VER) - email_list_entry = gdata.apps.EmailListEntry() - email_list_entry.email_list = gdata.apps.EmailList(name=list_name) - try: - return gdata.apps.EmailListEntryFromString( - str(self.Post(email_list_entry, uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def DeleteNickname(self, nickname): - """Delete a nickname""" - - uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname) - try: - self.Delete(uri) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def RetrievePageOfNicknames(self, start_nickname=None, - num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, - backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve one page of nicknames in the domain""" - - uri = "%s/nickname/%s" % (self._baseURL(), API_VER) - if start_nickname is not None: - uri += "?startNickname=%s" % start_nickname - try: - return gdata.apps.NicknameFeedFromString(str(self.GetWithRetries( - uri, num_retries=num_retries, delay=delay, backoff=backoff))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def GetGeneratorForAllNicknames( - self, num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve a generator for all nicknames in this domain.""" - first_page = self.RetrievePageOfNicknames(num_retries=num_retries, - delay=delay, - backoff=backoff) - return self.GetGeneratorFromLinkFinder( - first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries, - delay=delay, backoff=backoff) - - def RetrieveAllNicknames(self): - """Retrieve all nicknames in the domain""" - - ret = self.RetrievePageOfNicknames() - # pagination - return self.AddAllElementsFromAllPages( - ret, gdata.apps.NicknameFeedFromString) - - def GetGeneratorForAllNicknamesOfAUser( - self, user_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve a generator for all nicknames of a particular user.""" - uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name) - try: - first_page = gdata.apps.NicknameFeedFromString(str(self.GetWithRetries( - uri, num_retries=num_retries, delay=delay, backoff=backoff))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - return self.GetGeneratorFromLinkFinder( - first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries, - delay=delay, backoff=backoff) - - def RetrieveNicknames(self, user_name): - """Retrieve nicknames of the user""" - - uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name) - try: - ret = gdata.apps.NicknameFeedFromString(str(self.Get(uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - # pagination - return self.AddAllElementsFromAllPages( - ret, gdata.apps.NicknameFeedFromString) - - def RetrieveNickname(self, nickname): - """Retrieve a nickname. - - Args: - nickname: string The nickname to retrieve - - Returns: - gdata.apps.NicknameEntry - """ - - uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname) - try: - return gdata.apps.NicknameEntryFromString(str(self.Get(uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def CreateNickname(self, user_name, nickname): - """Create a nickname""" - - uri = "%s/nickname/%s" % (self._baseURL(), API_VER) - nickname_entry = gdata.apps.NicknameEntry() - nickname_entry.login = gdata.apps.Login(user_name=user_name) - nickname_entry.nickname = gdata.apps.Nickname(name=nickname) - - try: - return gdata.apps.NicknameEntryFromString( - str(self.Post(nickname_entry, uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def DeleteUser(self, user_name): - """Delete a user account""" - - uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) - try: - return self.Delete(uri) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def UpdateUser(self, user_name, user_entry): - """Update a user account.""" - - uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) - try: - return gdata.apps.UserEntryFromString(str(self.Put(user_entry, uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def CreateUser(self, user_name, family_name, given_name, password, - suspended='false', quota_limit=None, - password_hash_function=None, - change_password=None): - """Create a user account. """ - - uri = "%s/user/%s" % (self._baseURL(), API_VER) - user_entry = gdata.apps.UserEntry() - user_entry.login = gdata.apps.Login( - user_name=user_name, password=password, suspended=suspended, - hash_function_name=password_hash_function, - change_password=change_password) - user_entry.name = gdata.apps.Name(family_name=family_name, - given_name=given_name) - if quota_limit is not None: - user_entry.quota = gdata.apps.Quota(limit=str(quota_limit)) - - try: - return gdata.apps.UserEntryFromString(str(self.Post(user_entry, uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def SuspendUser(self, user_name): - user_entry = self.RetrieveUser(user_name) - if user_entry.login.suspended != 'true': - user_entry.login.suspended = 'true' - user_entry = self.UpdateUser(user_name, user_entry) - return user_entry - - def RestoreUser(self, user_name): - user_entry = self.RetrieveUser(user_name) - if user_entry.login.suspended != 'false': - user_entry.login.suspended = 'false' - user_entry = self.UpdateUser(user_name, user_entry) - return user_entry - - def RetrieveUser(self, user_name): - """Retrieve an user account. - - Args: - user_name: string The user name to retrieve - - Returns: - gdata.apps.UserEntry - """ - - uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name) - try: - return gdata.apps.UserEntryFromString(str(self.Get(uri))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def RetrievePageOfUsers(self, start_username=None, - num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, - backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve one page of users in this domain.""" - - uri = "%s/user/%s" % (self._baseURL(), API_VER) - if start_username is not None: - uri += "?startUsername=%s" % start_username - try: - return gdata.apps.UserFeedFromString(str(self.GetWithRetries( - uri, num_retries=num_retries, delay=delay, backoff=backoff))) - except gdata.service.RequestError as e: - raise AppsForYourDomainException(e.args[0]) - - def GetGeneratorForAllUsers(self, - num_retries=gdata.service.DEFAULT_NUM_RETRIES, - delay=gdata.service.DEFAULT_DELAY, - backoff=gdata.service.DEFAULT_BACKOFF): - """Retrieve a generator for all users in this domain.""" - first_page = self.RetrievePageOfUsers(num_retries=num_retries, delay=delay, - backoff=backoff) - return self.GetGeneratorFromLinkFinder( - first_page, gdata.apps.UserFeedFromString, num_retries=num_retries, - delay=delay, backoff=backoff) - - def RetrieveAllUsers(self): - """Retrieve all users in this domain. OBSOLETE""" - - ret = self.RetrievePageOfUsers() - # pagination - return self.AddAllElementsFromAllPages( - ret, gdata.apps.UserFeedFromString) - - -class PropertyService(gdata.service.GDataService): - """Client for the Google Apps Property service.""" - - def __init__(self, email=None, password=None, domain=None, source=None, - server='apps-apis.google.com', additional_headers=None): - gdata.service.GDataService.__init__(self, email=email, password=password, - service='apps', source=source, - server=server, - additional_headers=additional_headers) - self.ssl = True - self.port = 443 - self.domain = domain - - def AddAllElementsFromAllPages(self, link_finder, func): - """retrieve all pages and add all elements""" - next = link_finder.GetNextLink() - count = 0 - while next is not None: - next_feed = self.Get(next.href, converter=func) - count = count + len(next_feed.entry) - for a_entry in next_feed.entry: - link_finder.entry.append(a_entry) - next = next_feed.GetNextLink() - return link_finder - - def _GetPropertyEntry(self, properties): - property_entry = gdata.apps.PropertyEntry() - property = [] - for name, value in properties.items(): - if name is not None and value is not None: - property.append(gdata.apps.Property(name=name, value=value)) - property_entry.property = property - return property_entry - - def _PropertyEntry2Dict(self, property_entry): - properties = {} - for i, property in enumerate(property_entry.property): - properties[property.name] = property.value - return properties - - def _GetPropertyFeed(self, uri): - try: - return gdata.apps.PropertyFeedFromString(str(self.Get(uri))) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def _GetPropertiesList(self, uri): - property_feed = self._GetPropertyFeed(uri) - # pagination - property_feed = self.AddAllElementsFromAllPages( - property_feed, gdata.apps.PropertyFeedFromString) - properties_list = [] - for property_entry in property_feed.entry: - properties_list.append(self._PropertyEntry2Dict(property_entry)) - return properties_list - - def _GetProperties(self, uri): - try: - return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( - str(self.Get(uri)))) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def _PostProperties(self, uri, properties): - property_entry = self._GetPropertyEntry(properties) - try: - return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( - str(self.Post(property_entry, uri)))) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def _PutProperties(self, uri, properties): - property_entry = self._GetPropertyEntry(properties) - try: - return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString( - str(self.Put(property_entry, uri)))) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - def _DeleteProperties(self, uri): - try: - self.Delete(uri) - except gdata.service.RequestError as e: - raise gdata.apps.service.AppsForYourDomainException(e.args[0]) - - -def _bool2str(b): - if b is None: - return None - return str(b is True).lower() diff --git a/src/gam/gdata/service.py b/src/gam/gdata/service.py deleted file mode 100644 index 7efe0bf6..00000000 --- a/src/gam/gdata/service.py +++ /dev/null @@ -1,1714 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2006,2008 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -"""GDataService provides CRUD ops. and programmatic login for GData services. - - Error: A base exception class for all exceptions in the gdata_client - module. - - CaptchaRequired: This exception is thrown when a login attempt results in a - captcha challenge from the ClientLogin service. When this - exception is thrown, the captcha_token and captcha_url are - set to the values provided in the server's response. - - BadAuthentication: Raised when a login attempt is made with an incorrect - username or password. - - NotAuthenticated: Raised if an operation requiring authentication is called - before a user has authenticated. - - NonAuthSubToken: Raised if a method to modify an AuthSub token is used when - the user is either not authenticated or is authenticated - through another authentication mechanism. - - NonOAuthToken: Raised if a method to modify an OAuth token is used when the - user is either not authenticated or is authenticated through - another authentication mechanism. - - RequestError: Raised if a CRUD request returned a non-success code. - - UnexpectedReturnType: Raised if the response from the server was not of the - desired type. For example, this would be raised if the - server sent a feed when the client requested an entry. - - GDataService: Encapsulates user credentials needed to perform insert, update - and delete operations with the GData API. An instance can - perform user authentication, query, insertion, deletion, and - update. - - Query: Eases query URI creation by allowing URI parameters to be set as - dictionary attributes. For example a query with a feed of - '/base/feeds/snippets' and ['bq'] set to 'digital camera' will - produce '/base/feeds/snippets?bq=digital+camera' when .ToUri() is - called on it. -""" - - -__author__ = 'api.jscudder (Jeffrey Scudder)' - -import re -import urllib.request, urllib.parse, urllib.error -import urllib.parse -import lxml.etree as ElementTree -import atom.service -import gdata -import atom -import atom.http_interface -import atom.token_store - - -AUTH_SERVER_HOST = 'https://www.google.com' - - -# When requesting an AuthSub token, it is often helpful to track the scope -# which is being requested. One way to accomplish this is to add a URL -# parameter to the 'next' URL which contains the requested scope. This -# constant is the default name (AKA key) for the URL parameter. -SCOPE_URL_PARAM_NAME = 'authsub_token_scope' -# When requesting an OAuth access token or authorization of an existing OAuth -# request token, it is often helpful to track the scope(s) which is/are being -# requested. One way to accomplish this is to add a URL parameter to the -# 'callback' URL which contains the requested scope. This constant is the -# default name (AKA key) for the URL parameter. -OAUTH_SCOPE_URL_PARAM_NAME = 'oauth_token_scope' -# Default parameters for GDataService.GetWithRetries method -DEFAULT_NUM_RETRIES = 3 -DEFAULT_DELAY = 1 -DEFAULT_BACKOFF = 2 - - -def lookup_scopes(service_name): - """Finds the scope URLs for the desired service. - - In some cases, an unknown service may be used, and in those cases this - function will return None. - """ - if service_name in CLIENT_LOGIN_SCOPES: - return CLIENT_LOGIN_SCOPES[service_name] - return None - - -# Module level variable specifies which module should be used by GDataService -# objects to make HttpRequests. This setting can be overridden on each -# instance of GDataService. -# This module level variable is deprecated. Reassign the http_client member -# of a GDataService object instead. -http_request_handler = atom.service - - -class Error(Exception): - pass - - -class CaptchaRequired(Error): - pass - - -class BadAuthentication(Error): - pass - - -class NotAuthenticated(Error): - pass - - -class NonAuthSubToken(Error): - pass - - -class NonOAuthToken(Error): - pass - - -class RequestError(Error): - pass - - -class UnexpectedReturnType(Error): - pass - - -class BadAuthenticationServiceURL(Error): - pass - - -class FetchingOAuthRequestTokenFailed(RequestError): - pass - - -class TokenUpgradeFailed(RequestError): - pass - - -class RevokingOAuthTokenFailed(RequestError): - pass - - -class AuthorizationRequired(Error): - pass - - -class TokenHadNoScope(Error): - pass - - -class RanOutOfTries(Error): - pass - - -class GDataService(atom.service.AtomService): - """Contains elements needed for GData login and CRUD request headers. - - Maintains additional headers (tokens for example) needed for the GData - services to allow a user to perform inserts, updates, and deletes. - """ - # The hander member is deprecated, use http_client instead. - handler = None - # The auth_token member is deprecated, use the token_store instead. - auth_token = None - # The tokens dict is deprecated in favor of the token_store. - tokens = None - - def __init__(self, email=None, password=None, account_type='HOSTED_OR_GOOGLE', - service=None, auth_service_url=None, source=None, server=None, - additional_headers=None, handler=None, tokens=None, - http_client=None, token_store=None): - """Creates an object of type GDataService. - - Args: - email: string (optional) The user's email address, used for - authentication. - password: string (optional) The user's password. - account_type: string (optional) The type of account to use. Use - 'GOOGLE' for regular Google accounts or 'HOSTED' for Google - Apps accounts, or 'HOSTED_OR_GOOGLE' to try finding a HOSTED - account first and, if it doesn't exist, try finding a regular - GOOGLE account. Default value: 'HOSTED_OR_GOOGLE'. - service: string (optional) The desired service for which credentials - will be obtained. - auth_service_url: string (optional) User-defined auth token request URL - allows users to explicitly specify where to send auth token requests. - source: string (optional) The name of the user's application. - server: string (optional) The name of the server to which a connection - will be opened. Default value: 'base.google.com'. - additional_headers: dictionary (optional) Any additional headers which - should be included with CRUD operations. - handler: module (optional) This parameter is deprecated and has been - replaced by http_client. - tokens: This parameter is deprecated, calls should be made to - token_store instead. - http_client: An object responsible for making HTTP requests using a - request method. If none is provided, a new instance of - atom.http.ProxiedHttpClient will be used. - token_store: Keeps a collection of authorization tokens which can be - applied to requests for a specific URLs. Critical methods are - find_token based on a URL (atom.url.Url or a string), add_token, - and remove_token. - """ - atom.service.AtomService.__init__(self, http_client=http_client, - token_store=token_store) - self.email = email - self.password = password - self.account_type = account_type - self.service = service - self.auth_service_url = auth_service_url - self.server = server - self.additional_headers = additional_headers or {} - self._oauth_input_params = None - self.__SetSource(source) - self.__captcha_token = None - self.__captcha_url = None - self.__gsessionid = None - - if http_request_handler.__name__ == 'gdata.urlfetch': - import gdata.alt.appengine - self.http_client = gdata.alt.appengine.AppEngineHttpClient() - - def _SetSessionId(self, session_id): - """Used in unit tests to simulate a 302 which sets a gsessionid.""" - self.__gsessionid = session_id - - # Define properties for GDataService - def _SetAuthSubToken(self, auth_token, scopes=None): - """Deprecated, use SetAuthSubToken instead.""" - self.SetAuthSubToken(auth_token, scopes=scopes) - - def __SetAuthSubToken(self, auth_token, scopes=None): - """Deprecated, use SetAuthSubToken instead.""" - self._SetAuthSubToken(auth_token, scopes=scopes) - - def _GetAuthToken(self): - """Returns the auth token used for authenticating requests. - - Returns: - string - """ - current_scopes = lookup_scopes(self.service) - if current_scopes: - token = self.token_store.find_token(current_scopes[0]) - if hasattr(token, 'auth_header'): - return token.auth_header - return None - - def _GetCaptchaToken(self): - """Returns a captcha token if the most recent login attempt generated one. - - The captcha token is only set if the Programmatic Login attempt failed - because the Google service issued a captcha challenge. - - Returns: - string - """ - return self.__captcha_token - - def __GetCaptchaToken(self): - return self._GetCaptchaToken() - - captcha_token = property(__GetCaptchaToken, - doc="""Get the captcha token for a login request.""") - - def _GetCaptchaURL(self): - """Returns the URL of the captcha image if a login attempt generated one. - - The captcha URL is only set if the Programmatic Login attempt failed - because the Google service issued a captcha challenge. - - Returns: - string - """ - return self.__captcha_url - - def __GetCaptchaURL(self): - return self._GetCaptchaURL() - - captcha_url = property(__GetCaptchaURL, - doc="""Get the captcha URL for a login request.""") - - def GetGeneratorFromLinkFinder(self, link_finder, func, - num_retries=DEFAULT_NUM_RETRIES, - delay=DEFAULT_DELAY, - backoff=DEFAULT_BACKOFF): - """returns a generator for pagination""" - yield link_finder - next = link_finder.GetNextLink() - while next is not None: - next_feed = func(str(self.GetWithRetries( - next.href, num_retries=num_retries, delay=delay, backoff=backoff))) - yield next_feed - next = next_feed.GetNextLink() - - def _GetElementGeneratorFromLinkFinder(self, link_finder, func, - num_retries=DEFAULT_NUM_RETRIES, - delay=DEFAULT_DELAY, - backoff=DEFAULT_BACKOFF): - for element in self.GetGeneratorFromLinkFinder(link_finder, func, - num_retries=num_retries, - delay=delay, - backoff=backoff).entry: - yield element - - def GetOAuthInputParameters(self): - return self._oauth_input_params - - def SetOAuthInputParameters(self, signature_method, consumer_key, - consumer_secret=None, rsa_key=None, - two_legged_oauth=False, requestor_id=None): - """Sets parameters required for using OAuth authentication mechanism. - - NOTE: Though consumer_secret and rsa_key are optional, either of the two - is required depending on the value of the signature_method. - - Args: - signature_method: class which provides implementation for strategy class - oauth.oauth.OAuthSignatureMethod. Signature method to be used for - signing each request. Valid implementations are provided as the - constants defined by gdata.auth.OAuthSignatureMethod. Currently - they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and - gdata.auth.OAuthSignatureMethod.HMAC_SHA1 - consumer_key: string Domain identifying third_party web application. - consumer_secret: string (optional) Secret generated during registration. - Required only for HMAC_SHA1 signature method. - rsa_key: string (optional) Private key required for RSA_SHA1 signature - method. - two_legged_oauth: boolean (optional) Enables two-legged OAuth process. - requestor_id: string (optional) User email adress to make requests on - their behalf. This parameter should only be set when two_legged_oauth - is True. - """ - self._oauth_input_params = gdata.auth.OAuthInputParams( - signature_method, consumer_key, consumer_secret=consumer_secret, - rsa_key=rsa_key, requestor_id=requestor_id) - if two_legged_oauth: - oauth_token = gdata.auth.OAuthToken( - oauth_input_params=self._oauth_input_params) - self.SetOAuthToken(oauth_token) - - def FetchOAuthRequestToken(self, scopes=None, extra_parameters=None, - request_url='%s/accounts/OAuthGetRequestToken' % \ - AUTH_SERVER_HOST, oauth_callback=None): - """Fetches and sets the OAuth request token and returns it. - - Args: - scopes: string or list of string base URL(s) of the service(s) to be - accessed. If None, then this method tries to determine the - scope(s) from the current service. - extra_parameters: dict (optional) key-value pairs as any additional - parameters to be included in the URL and signature while making a - request for fetching an OAuth request token. All the OAuth parameters - are added by default. But if provided through this argument, any - default parameters will be overwritten. For e.g. a default parameter - oauth_version 1.0 can be overwritten if - extra_parameters = {'oauth_version': '2.0'} - request_url: Request token URL. The default is - 'https://www.google.com/accounts/OAuthGetRequestToken'. - oauth_callback: str (optional) If set, it is assume the client is using - the OAuth v1.0a protocol where the callback url is sent in the - request token step. If the oauth_callback is also set in - extra_params, this value will override that one. - - Returns: - The fetched request token as a gdata.auth.OAuthToken object. - - Raises: - FetchingOAuthRequestTokenFailed if the server responded to the request - with an error. - """ - if scopes is None: - scopes = lookup_scopes(self.service) - if not isinstance(scopes, (list, tuple)): - scopes = [scopes,] - if oauth_callback: - if extra_parameters is not None: - extra_parameters['oauth_callback'] = oauth_callback - else: - extra_parameters = {'oauth_callback': oauth_callback} - request_token_url = gdata.auth.GenerateOAuthRequestTokenUrl( - self._oauth_input_params, scopes, - request_token_url=request_url, - extra_parameters=extra_parameters) - response = self.http_client.request('GET', str(request_token_url)) - if response.status == 200: - token = gdata.auth.OAuthToken() - token.set_token_string(response.read()) - token.scopes = scopes - token.oauth_input_params = self._oauth_input_params - self.SetOAuthToken(token) - return token - error = { - 'status': response.status, - 'reason': 'Non 200 response on fetch request token', - 'body': response.read() - } - raise FetchingOAuthRequestTokenFailed(error) - - def SetOAuthToken(self, oauth_token): - """Attempts to set the current token and add it to the token store. - - The oauth_token can be any OAuth token i.e. unauthorized request token, - authorized request token or access token. - This method also attempts to add the token to the token store. - Use this method any time you want the current token to point to the - oauth_token passed. For e.g. call this method with the request token - you receive from FetchOAuthRequestToken. - - Args: - request_token: gdata.auth.OAuthToken OAuth request token. - """ - if self.auto_set_current_token: - self.current_token = oauth_token - if self.auto_store_tokens: - self.token_store.add_token(oauth_token) - - def GenerateOAuthAuthorizationURL( - self, request_token=None, callback_url=None, extra_params=None, - include_scopes_in_callback=False, - scopes_param_prefix=OAUTH_SCOPE_URL_PARAM_NAME, - request_url='%s/accounts/OAuthAuthorizeToken' % AUTH_SERVER_HOST): - """Generates URL at which user will login to authorize the request token. - - Args: - request_token: gdata.auth.OAuthToken (optional) OAuth request token. - If not specified, then the current token will be used if it is of - type , else it is found by looking in the - token_store by looking for a token for the current scope. - callback_url: string (optional) The URL user will be sent to after - logging in and granting access. - extra_params: dict (optional) Additional parameters to be sent. - include_scopes_in_callback: Boolean (default=False) if set to True, and - if 'callback_url' is present, the 'callback_url' will be modified to - include the scope(s) from the request token as a URL parameter. The - key for the 'callback' URL's scope parameter will be - OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as - a parameter to the 'callback' URL, is that the page which receives - the OAuth token will be able to tell which URLs the token grants - access to. - scopes_param_prefix: string (default='oauth_token_scope') The URL - parameter key which maps to the list of valid scopes for the token. - This URL parameter will be included in the callback URL along with - the scopes of the token as value if include_scopes_in_callback=True. - request_url: Authorization URL. The default is - 'https://www.google.com/accounts/OAuthAuthorizeToken'. - Returns: - A string URL at which the user is required to login. - - Raises: - NonOAuthToken if the user's request token is not an OAuth token or if a - request token was not available. - """ - if request_token and not isinstance(request_token, gdata.auth.OAuthToken): - raise NonOAuthToken - if not request_token: - if isinstance(self.current_token, gdata.auth.OAuthToken): - request_token = self.current_token - else: - current_scopes = lookup_scopes(self.service) - if current_scopes: - token = self.token_store.find_token(current_scopes[0]) - if isinstance(token, gdata.auth.OAuthToken): - request_token = token - if not request_token: - raise NonOAuthToken - return str(gdata.auth.GenerateOAuthAuthorizationUrl( - request_token, - authorization_url=request_url, - callback_url=callback_url, extra_params=extra_params, - include_scopes_in_callback=include_scopes_in_callback, - scopes_param_prefix=scopes_param_prefix)) - - def UpgradeToOAuthAccessToken(self, authorized_request_token=None, - request_url='%s/accounts/OAuthGetAccessToken' \ - % AUTH_SERVER_HOST, oauth_version='1.0', - oauth_verifier=None): - """Upgrades the authorized request token to an access token and returns it - - Args: - authorized_request_token: gdata.auth.OAuthToken (optional) OAuth request - token. If not specified, then the current token will be used if it is - of type , else it is found by looking in the - token_store by looking for a token for the current scope. - request_url: Access token URL. The default is - 'https://www.google.com/accounts/OAuthGetAccessToken'. - oauth_version: str (default='1.0') oauth_version parameter. All other - 'oauth_' parameters are added by default. This parameter too, is - added by default but here you can override it's value. - oauth_verifier: str (optional) If present, it is assumed that the client - will use the OAuth v1.0a protocol which includes passing the - oauth_verifier (as returned by the SP) in the access token step. - - Returns: - Access token - - Raises: - NonOAuthToken if the user's authorized request token is not an OAuth - token or if an authorized request token was not available. - TokenUpgradeFailed if the server responded to the request with an - error. - """ - if (authorized_request_token and - not isinstance(authorized_request_token, gdata.auth.OAuthToken)): - raise NonOAuthToken - if not authorized_request_token: - if isinstance(self.current_token, gdata.auth.OAuthToken): - authorized_request_token = self.current_token - else: - current_scopes = lookup_scopes(self.service) - if current_scopes: - token = self.token_store.find_token(current_scopes[0]) - if isinstance(token, gdata.auth.OAuthToken): - authorized_request_token = token - if not authorized_request_token: - raise NonOAuthToken - access_token_url = gdata.auth.GenerateOAuthAccessTokenUrl( - authorized_request_token, - self._oauth_input_params, - access_token_url=request_url, - oauth_version=oauth_version, - oauth_verifier=oauth_verifier) - response = self.http_client.request('GET', str(access_token_url)) - if response.status == 200: - token = gdata.auth.OAuthTokenFromHttpBody(response.read()) - token.scopes = authorized_request_token.scopes - token.oauth_input_params = authorized_request_token.oauth_input_params - self.SetOAuthToken(token) - return token - else: - raise TokenUpgradeFailed({'status': response.status, - 'reason': 'Non 200 response on upgrade', - 'body': response.read()}) - - def RevokeOAuthToken(self, request_url='%s/accounts/AuthSubRevokeToken' % \ - AUTH_SERVER_HOST): - """Revokes an existing OAuth token. - - request_url: Token revoke URL. The default is - 'https://www.google.com/accounts/AuthSubRevokeToken'. - Raises: - NonOAuthToken if the user's auth token is not an OAuth token. - RevokingOAuthTokenFailed if request for revoking an OAuth token failed. - """ - scopes = lookup_scopes(self.service) - token = self.token_store.find_token(scopes[0]) - if not isinstance(token, gdata.auth.OAuthToken): - raise NonOAuthToken - - response = token.perform_request(self.http_client, 'GET', request_url, - headers={'Content-Type':'application/x-www-form-urlencoded'}) - if response.status == 200: - self.token_store.remove_token(token) - else: - raise RevokingOAuthTokenFailed - - def GetAuthSubToken(self): - """Returns the AuthSub token as a string. - - If the token is an gdta.auth.AuthSubToken, the Authorization Label - ("AuthSub token") is removed. - - This method examines the current_token to see if it is an AuthSubToken - or SecureAuthSubToken. If not, it searches the token_store for a token - which matches the current scope. - - The current scope is determined by the service name string member. - - Returns: - If the current_token is set to an AuthSubToken/SecureAuthSubToken, - return the token string. If there is no current_token, a token string - for a token which matches the service object's default scope is returned. - If there are no tokens valid for the scope, returns None. - """ - if isinstance(self.current_token, gdata.auth.AuthSubToken): - return self.current_token.get_token_string() - current_scopes = lookup_scopes(self.service) - if current_scopes: - token = self.token_store.find_token(current_scopes[0]) - if isinstance(token, gdata.auth.AuthSubToken): - return token.get_token_string() - else: - token = self.token_store.find_token(atom.token_store.SCOPE_ALL) - if isinstance(token, gdata.auth.ClientLoginToken): - return token.get_token_string() - return None - - def SetAuthSubToken(self, token, scopes=None, rsa_key=None): - """Sets the token sent in requests to an AuthSub token. - - Sets the current_token and attempts to add the token to the token_store. - - Only use this method if you have received a token from the AuthSub - service. The auth token is set automatically when UpgradeToSessionToken() - is used. See documentation for Google AuthSub here: - http://code.google.com/apis/accounts/AuthForWebApps.html - - Args: - token: gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken or string - The token returned by the AuthSub service. If the token is an - AuthSubToken or SecureAuthSubToken, the scope information stored in - the token is used. If the token is a string, the scopes parameter is - used to determine the valid scopes. - scopes: list of URLs for which the token is valid. This is only used - if the token parameter is a string. - rsa_key: string (optional) Private key required for RSA_SHA1 signature - method. This parameter is necessary if the token is a string - representing a secure token. - """ - if not isinstance(token, gdata.auth.AuthSubToken): - token_string = token - if rsa_key: - token = gdata.auth.SecureAuthSubToken(rsa_key) - else: - token = gdata.auth.AuthSubToken() - - token.set_token_string(token_string) - - # If no scopes were set for the token, use the scopes passed in, or - # try to determine the scopes based on the current service name. If - # all else fails, set the token to match all requests. - if not token.scopes: - if scopes is None: - scopes = lookup_scopes(self.service) - if scopes is None: - scopes = [atom.token_store.SCOPE_ALL] - token.scopes = scopes - if self.auto_set_current_token: - self.current_token = token - if self.auto_store_tokens: - self.token_store.add_token(token) - - def GetClientLoginToken(self): - """Returns the token string for the current token or a token matching the - service scope. - - If the current_token is a ClientLoginToken, the token string for - the current token is returned. If the current_token is not set, this method - searches for a token in the token_store which is valid for the service - object's current scope. - - The current scope is determined by the service name string member. - The token string is the end of the Authorization header, it doesn not - include the ClientLogin label. - """ - if isinstance(self.current_token, gdata.auth.ClientLoginToken): - return self.current_token.get_token_string() - current_scopes = lookup_scopes(self.service) - if current_scopes: - token = self.token_store.find_token(current_scopes[0]) - if isinstance(token, gdata.auth.ClientLoginToken): - return token.get_token_string() - else: - token = self.token_store.find_token(atom.token_store.SCOPE_ALL) - if isinstance(token, gdata.auth.ClientLoginToken): - return token.get_token_string() - return None - - def SetClientLoginToken(self, token, scopes=None): - """Sets the token sent in requests to a ClientLogin token. - - This method sets the current_token to a new ClientLoginToken and it - also attempts to add the ClientLoginToken to the token_store. - - Only use this method if you have received a token from the ClientLogin - service. The auth_token is set automatically when ProgrammaticLogin() - is used. See documentation for Google ClientLogin here: - http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html - - Args: - token: string or instance of a ClientLoginToken. - """ - if not isinstance(token, gdata.auth.ClientLoginToken): - token_string = token - token = gdata.auth.ClientLoginToken() - token.set_token_string(token_string) - - if not token.scopes: - if scopes is None: - scopes = lookup_scopes(self.service) - if scopes is None: - scopes = [atom.token_store.SCOPE_ALL] - token.scopes = scopes - if self.auto_set_current_token: - self.current_token = token - if self.auto_store_tokens: - self.token_store.add_token(token) - - # Private methods to create the source property. - def __GetSource(self): - return self.__source - - def __SetSource(self, new_source): - self.__source = new_source - # Update the UserAgent header to include the new application name. - self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % ( - self.__source,) - - source = property(__GetSource, __SetSource, - doc="""The source is the name of the application making the request. - It should be in the form company_id-app_name-app_version""") - - # Authentication operations - - def ProgrammaticLogin(self, captcha_token=None, captcha_response=None): - """Authenticates the user and sets the GData Auth token. - - Login retreives a temporary auth token which must be used with all - requests to GData services. The auth token is stored in the GData client - object. - - Login is also used to respond to a captcha challenge. If the user's login - attempt failed with a CaptchaRequired error, the user can respond by - calling Login with the captcha token and the answer to the challenge. - - Args: - captcha_token: string (optional) The identifier for the captcha challenge - which was presented to the user. - captcha_response: string (optional) The user's answer to the captch - challenge. - - Raises: - CaptchaRequired if the login service will require a captcha response - BadAuthentication if the login service rejected the username or password - Error if the login service responded with a 403 different from the above - """ - request_body = gdata.auth.generate_client_login_request_body(self.email, - self.password, self.service, self.source, self.account_type, - captcha_token, captcha_response) - - # If the user has defined their own authentication service URL, - # send the ClientLogin requests to this URL: - if not self.auth_service_url: - auth_request_url = AUTH_SERVER_HOST + '/accounts/ClientLogin' - else: - auth_request_url = self.auth_service_url - - auth_response = self.http_client.request('POST', auth_request_url, - data=request_body, - headers={'Content-Type':'application/x-www-form-urlencoded'}) - response_body = auth_response.read() - - if auth_response.status == 200: - # TODO: insert the token into the token_store directly. - self.SetClientLoginToken( - gdata.auth.get_client_login_token(response_body)) - self.__captcha_token = None - self.__captcha_url = None - - elif auth_response.status == 403: - # Examine each line to find the error type and the captcha token and - # captch URL if they are present. - captcha_parameters = gdata.auth.get_captcha_challenge(response_body, - captcha_base_url='%s/accounts/' % AUTH_SERVER_HOST) - if captcha_parameters: - self.__captcha_token = captcha_parameters['token'] - self.__captcha_url = captcha_parameters['url'] - raise CaptchaRequired('Captcha Required') - elif response_body.splitlines()[0] == 'Error=BadAuthentication': - self.__captcha_token = None - self.__captcha_url = None - raise BadAuthentication('Incorrect username or password') - else: - self.__captcha_token = None - self.__captcha_url = None - raise Error('Server responded with a 403 code') - elif auth_response.status == 302: - self.__captcha_token = None - self.__captcha_url = None - # Google tries to redirect all bad URLs back to - # http://www.google.. If a redirect - # attempt is made, assume the user has supplied an incorrect authentication URL - raise BadAuthenticationServiceURL('Server responded with a 302 code.') - - def ClientLogin(self, username, password, account_type=None, service=None, - auth_service_url=None, source=None, captcha_token=None, - captcha_response=None): - """Convenience method for authenticating using ProgrammaticLogin. - - Sets values for email, password, and other optional members. - - Args: - username: - password: - account_type: string (optional) - service: string (optional) - auth_service_url: string (optional) - captcha_token: string (optional) - captcha_response: string (optional) - """ - self.email = username - self.password = password - - if account_type: - self.account_type = account_type - if service: - self.service = service - if source: - self.source = source - if auth_service_url: - self.auth_service_url = auth_service_url - - self.ProgrammaticLogin(captcha_token, captcha_response) - - def GenerateAuthSubURL(self, next, scope, secure=False, session=True, - domain='default'): - """Generate a URL at which the user will login and be redirected back. - - Users enter their credentials on a Google login page and a token is sent - to the URL specified in next. See documentation for AuthSub login at: - http://code.google.com/apis/accounts/docs/AuthSub.html - - Args: - next: string The URL user will be sent to after logging in. - scope: string or list of strings. The URLs of the services to be - accessed. - secure: boolean (optional) Determines whether or not the issued token - is a secure token. - session: boolean (optional) Determines whether or not the issued token - can be upgraded to a session token. - """ - if not isinstance(scope, (list, tuple)): - scope = (scope,) - return gdata.auth.generate_auth_sub_url(next, scope, secure=secure, - session=session, - request_url='%s/accounts/AuthSubRequest' % AUTH_SERVER_HOST, - domain=domain) - - def UpgradeToSessionToken(self, token=None): - """Upgrades a single use AuthSub token to a session token. - - Args: - token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken - (optional) which is good for a single use but can be upgraded - to a session token. If no token is passed in, the token - is found by looking in the token_store by looking for a token - for the current scope. - - Raises: - NonAuthSubToken if the user's auth token is not an AuthSub token - TokenUpgradeFailed if the server responded to the request with an - error. - """ - if token is None: - scopes = lookup_scopes(self.service) - if scopes: - token = self.token_store.find_token(scopes[0]) - else: - token = self.token_store.find_token(atom.token_store.SCOPE_ALL) - if not isinstance(token, gdata.auth.AuthSubToken): - raise NonAuthSubToken - - self.SetAuthSubToken(self.upgrade_to_session_token(token)) - - def upgrade_to_session_token(self, token): - """Upgrades a single use AuthSub token to a session token. - - Args: - token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken - which is good for a single use but can be upgraded to a - session token. - - Returns: - The upgraded token as a gdata.auth.AuthSubToken object. - - Raises: - TokenUpgradeFailed if the server responded to the request with an - error. - """ - response = token.perform_request(self.http_client, 'GET', - AUTH_SERVER_HOST + '/accounts/AuthSubSessionToken', - headers={'Content-Type':'application/x-www-form-urlencoded'}) - response_body = response.read() - if response.status == 200: - token.set_token_string( - gdata.auth.token_from_http_body(response_body)) - return token - else: - raise TokenUpgradeFailed({'status': response.status, - 'reason': 'Non 200 response on upgrade', - 'body': response_body}) - - def RevokeAuthSubToken(self): - """Revokes an existing AuthSub token. - - Raises: - NonAuthSubToken if the user's auth token is not an AuthSub token - """ - scopes = lookup_scopes(self.service) - token = self.token_store.find_token(scopes[0]) - if not isinstance(token, gdata.auth.AuthSubToken): - raise NonAuthSubToken - - response = token.perform_request(self.http_client, 'GET', - AUTH_SERVER_HOST + '/accounts/AuthSubRevokeToken', - headers={'Content-Type':'application/x-www-form-urlencoded'}) - if response.status == 200: - self.token_store.remove_token(token) - - def AuthSubTokenInfo(self): - """Fetches the AuthSub token's metadata from the server. - - Raises: - NonAuthSubToken if the user's auth token is not an AuthSub token - """ - scopes = lookup_scopes(self.service) - token = self.token_store.find_token(scopes[0]) - if not isinstance(token, gdata.auth.AuthSubToken): - raise NonAuthSubToken - - response = token.perform_request(self.http_client, 'GET', - AUTH_SERVER_HOST + '/accounts/AuthSubTokenInfo', - headers={'Content-Type':'application/x-www-form-urlencoded'}) - result_body = response.read() - if response.status == 200: - return result_body - else: - raise RequestError({'status': response.status, - 'body': result_body}) - - def GetWithRetries(self, uri, extra_headers=None, redirects_remaining=4, - encoding='UTF-8', converter=None, num_retries=DEFAULT_NUM_RETRIES, - delay=DEFAULT_DELAY, backoff=DEFAULT_BACKOFF, logger=None): - """This is a wrapper method for Get with retrying capability. - - To avoid various errors while retrieving bulk entities by retrying - specified times. - - Note this method relies on the time module and so may not be usable - by default in Python2.2. - - Args: - num_retries: Integer; the retry count. - delay: Integer; the initial delay for retrying. - backoff: Integer; how much the delay should lengthen after each failure. - logger: An object which has a debug(str) method to receive logging - messages. Recommended that you pass in the logging module. - Raises: - ValueError if any of the parameters has an invalid value. - RanOutOfTries on failure after number of retries. - """ - # Moved import for time module inside this method since time is not a - # default module in Python2.2. This method will not be usable in - # Python2.2. - import time - if backoff <= 1: - raise ValueError("backoff must be greater than 1") - num_retries = int(num_retries) - - if num_retries < 0: - raise ValueError("num_retries must be 0 or greater") - - if delay <= 0: - raise ValueError("delay must be greater than 0") - - # Let's start - mtries, mdelay = num_retries, delay - while mtries > 0: - if mtries != num_retries: - if logger: - logger.debug("Retrying: %s" % uri) - try: - rv = self.Get(uri, extra_headers=extra_headers, - redirects_remaining=redirects_remaining, - encoding=encoding, converter=converter) - except SystemExit: - # Allow this error - raise - except RequestError as e: - # Error 500 is 'internal server error' and warrants a retry - # Error 503 is 'service unavailable' and warrants a retry - if e[0]['status'] not in [500, 503]: - raise e - # Else, fall through to the retry code... - except Exception as e: - if logger: - logger.debug(e) - # Fall through to the retry code... - else: - # This is the right path. - return rv - mtries -= 1 - time.sleep(mdelay) - mdelay *= backoff - raise RanOutOfTries('Ran out of tries.') - - # CRUD operations - def Get(self, uri, extra_headers=None, url_params=None, - escape_params=True, redirects_remaining=4, - encoding='UTF-8', converter=None): - """Query the GData API with the given URI - - The uri is the portion of the URI after the server value - (ex: www.google.com). - - To perform a query against Google Base, set the server to - 'base.google.com' and set the uri to '/base/feeds/...', where ... is - your query. For example, to find snippets for all digital cameras uri - should be set to: '/base/feeds/snippets?bq=digital+camera' - - Args: - uri: string The query in the form of a URI. Example: - '/base/feeds/snippets?bq=digital+camera'. - extra_headers: dictionary (optional) Extra HTTP headers to be included - in the GET request. These headers are in addition to - those stored in the client's additional_headers property. - The client automatically sets the Content-Type and - Authorization headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - redirects_remaining: int (optional) Tracks the number of additional - redirects this method will allow. If the service object receives - a redirect and remaining is 0, it will not follow the redirect. - This was added to avoid infinite redirect loops. - encoding: string (optional) The character encoding for the server's - response. Default is UTF-8 - converter: func (optional) A function which will transform - the server's results before it is returned. Example: use - GDataFeedFromString to parse the server response as if it - were a GDataFeed. - - Returns: - If there is no ResultsTransformer specified in the call, a GDataFeed - or GDataEntry depending on which is sent from the server. If the - response is niether a feed or entry and there is no ResultsTransformer, - return a string. If there is a ResultsTransformer, the returned value - will be that of the ResultsTransformer function. - """ - - if extra_headers is None: - extra_headers = {} - - if self.__gsessionid is not None: - if uri.find('gsessionid=') < 0: - if uri.find('?') > -1: - uri += '&gsessionid=%s' % (self.__gsessionid,) - else: - uri += '?gsessionid=%s' % (self.__gsessionid,) - - server_response = self.request('GET', uri, - headers=extra_headers, url_params=url_params) - result_body = server_response.read() - - if server_response.status == 200: - if converter: - return converter(result_body) - # There was no ResultsTransformer specified, so try to convert the - # server's response into a GDataFeed. - feed = gdata.GDataFeedFromString(result_body) - if not feed: - # If conversion to a GDataFeed failed, try to convert the server's - # response to a GDataEntry. - entry = gdata.GDataEntryFromString(result_body) - if not entry: - # The server's response wasn't a feed, or an entry, so return the - # response body as a string. - return result_body - return entry - return feed - elif server_response.status == 302: - if redirects_remaining > 0: - location = (server_response.getheader('Location') - or server_response.getheader('location')) - if location is not None: - m = re.compile(r'[?&]gsessionid=(\w*)').search(location) - if m is not None: - self.__gsessionid = m.group(1) - return GDataService.Get(self, location, extra_headers, redirects_remaining - 1, - encoding=encoding, converter=converter) - else: - raise RequestError({'status': server_response.status, - 'reason': '302 received without Location header', - 'body': result_body}) - else: - raise RequestError({'status': server_response.status, - 'reason': 'Redirect received, but redirects_remaining <= 0', - 'body': result_body}) - else: - raise RequestError({'status': server_response.status, - 'reason': server_response.reason, 'body': result_body}) - - def GetMedia(self, uri, extra_headers=None): - """Returns a MediaSource containing media and its metadata from the given - URI string. - """ - response_handle = self.request('GET', uri, - headers=extra_headers, url_params=url_params) - return gdata.MediaSource(response_handle, response_handle.getheader( - 'Content-Type'), - response_handle.getheader('Content-Length')) - - def GetEntry(self, uri, extra_headers=None): - """Query the GData API with the given URI and receive an Entry. - - See also documentation for gdata.service.Get - - Args: - uri: string The query in the form of a URI. Example: - '/base/feeds/snippets?bq=digital+camera'. - extra_headers: dictionary (optional) Extra HTTP headers to be included - in the GET request. These headers are in addition to - those stored in the client's additional_headers property. - The client automatically sets the Content-Type and - Authorization headers. - - Returns: - A GDataEntry built from the XML in the server's response. - """ - - result = GDataService.Get(self, uri, extra_headers, - converter=atom.EntryFromString) - if isinstance(result, atom.Entry): - return result - else: - raise UnexpectedReturnType('Server did not send an entry') - - def GetFeed(self, uri, extra_headers=None, - converter=gdata.GDataFeedFromString): - """Query the GData API with the given URI and receive a Feed. - - See also documentation for gdata.service.Get - - Args: - uri: string The query in the form of a URI. Example: - '/base/feeds/snippets?bq=digital+camera'. - extra_headers: dictionary (optional) Extra HTTP headers to be included - in the GET request. These headers are in addition to - those stored in the client's additional_headers property. - The client automatically sets the Content-Type and - Authorization headers. - - Returns: - A GDataFeed built from the XML in the server's response. - """ - - result = GDataService.Get(self, uri, extra_headers, converter=converter) - if isinstance(result, atom.Feed): - return result - else: - raise UnexpectedReturnType('Server did not send a feed') - - def GetNext(self, feed): - """Requests the next 'page' of results in the feed. - - This method uses the feed's next link to request an additional feed - and uses the class of the feed to convert the results of the GET request. - - Args: - feed: atom.Feed or a subclass. The feed should contain a next link and - the type of the feed will be applied to the results from the - server. The new feed which is returned will be of the same class - as this feed which was passed in. - - Returns: - A new feed representing the next set of results in the server's feed. - The type of this feed will match that of the feed argument. - """ - next_link = feed.GetNextLink() - # Create a closure which will convert an XML string to the class of - # the feed object passed in. - def ConvertToFeedClass(xml_string): - return atom.CreateClassFromXMLString(feed.__class__, xml_string) - # Make a GET request on the next link and use the above closure for the - # converted which processes the XML string from the server. - if next_link and next_link.href: - return GDataService.Get(self, next_link.href, - converter=ConvertToFeedClass) - else: - return None - - def Post(self, data, uri, extra_headers=None, url_params=None, - escape_params=True, redirects_remaining=4, media_source=None, - converter=None): - """Insert or update data into a GData service at the given URI. - - Args: - data: string, ElementTree._Element, atom.Entry, or gdata.GDataEntry The - XML to be sent to the uri. - uri: string The location (feed) to which the data should be inserted. - Example: '/base/feeds/items'. - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type, - Authorization, and Content-Length headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - media_source: MediaSource (optional) Container for the media to be sent - along with the entry, if provided. - converter: func (optional) A function which will be executed on the - server's response. Often this is a function like - GDataEntryFromString which will parse the body of the server's - response and return a GDataEntry. - - Returns: - If the post succeeded, this method will return a GDataFeed, GDataEntry, - or the results of running converter on the server's result body (if - converter was specified). - """ - return GDataService.PostOrPut(self, 'POST', data, uri, - extra_headers=extra_headers, url_params=url_params, - escape_params=escape_params, redirects_remaining=redirects_remaining, - media_source=media_source, converter=converter) - - def PostOrPut(self, verb, data, uri, extra_headers=None, url_params=None, - escape_params=True, redirects_remaining=4, media_source=None, - converter=None): - """Insert data into a GData service at the given URI. - - Args: - verb: string, either 'POST' or 'PUT' - data: string, ElementTree._Element, atom.Entry, or gdata.GDataEntry The - XML to be sent to the uri. - uri: string The location (feed) to which the data should be inserted. - Example: '/base/feeds/items'. - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type, - Authorization, and Content-Length headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - media_source: MediaSource (optional) Container for the media to be sent - along with the entry, if provided. - converter: func (optional) A function which will be executed on the - server's response. Often this is a function like - GDataEntryFromString which will parse the body of the server's - response and return a GDataEntry. - - Returns: - If the post succeeded, this method will return a GDataFeed, GDataEntry, - or the results of running converter on the server's result body (if - converter was specified). - """ - if extra_headers is None: - extra_headers = {} - - if self.__gsessionid is not None: - if uri.find('gsessionid=') < 0: - if url_params is None: - url_params = {} - url_params['gsessionid'] = self.__gsessionid - - if data and media_source: - if ElementTree.iselement(data): - data_str = ElementTree.tostring(data) - else: - data_str = str(data) - - multipart = [] - multipart.append('Media multipart posting\r\n--END_OF_PART\r\n' + \ - 'Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n') - multipart.append('\r\n--END_OF_PART\r\nContent-Type: ' + \ - media_source.content_type+'\r\n\r\n') - multipart.append('\r\n--END_OF_PART--\r\n') - - extra_headers['MIME-version'] = '1.0' - extra_headers['Content-Length'] = str(len(multipart[0]) + - len(multipart[1]) + len(multipart[2]) + - len(data_str) + media_source.content_length) - - extra_headers['Content-Type'] = 'multipart/related; boundary=END_OF_PART' - server_response = self.request(verb, uri, - data=[multipart[0], data_str, multipart[1], media_source.file_handle, - multipart[2]], headers=extra_headers, url_params=url_params) - result_body = server_response.read() - - elif media_source or isinstance(data, gdata.MediaSource): - if isinstance(data, gdata.MediaSource): - media_source = data - extra_headers['Content-Length'] = str(media_source.content_length) - extra_headers['Content-Type'] = media_source.content_type - server_response = self.request(verb, uri, - data=media_source.file_handle, headers=extra_headers, - url_params=url_params) - result_body = server_response.read() - - else: - http_data = data - #content_type = 'application/atom+xml; charset=UTF-8' - #extra_headers['Content-Type'] = content_type - server_response = self.request(verb, uri, data=http_data, - headers=extra_headers, url_params=url_params) - result_body = server_response.read() - - # Server returns 201 for most post requests, but when performing a batch - # request the server responds with a 200 on success. - if server_response.status == 201 or server_response.status == 200: - if converter: - return converter(result_body) - feed = gdata.GDataFeedFromString(result_body) - if not feed: - entry = gdata.GDataEntryFromString(result_body) - if not entry: - return result_body - return entry - return feed - elif server_response.status == 302: - if redirects_remaining > 0: - location = (server_response.getheader('Location') - or server_response.getheader('location')) - if location is not None: - m = re.compile(r'[?&]gsessionid=(\w*)').search(location) - if m is not None: - self.__gsessionid = m.group(1) - return GDataService.PostOrPut(self, verb, data, location, - extra_headers, url_params, escape_params, - redirects_remaining - 1, media_source, converter=converter) - else: - raise RequestError({'status': server_response.status, - 'reason': '302 received without Location header', - 'body': result_body}) - else: - raise RequestError({'status': server_response.status, - 'reason': 'Redirect received, but redirects_remaining <= 0', - 'body': result_body}) - else: - raise RequestError({'status': server_response.status, - 'reason': server_response.reason, 'body': result_body}) - - def Put(self, data, uri, extra_headers=None, url_params=None, - escape_params=True, redirects_remaining=3, media_source=None, - converter=None): - """Updates an entry at the given URI. - - Args: - data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The - XML containing the updated data. - uri: string A URI indicating entry to which the update will be applied. - Example: '/base/feeds/items/ITEM-ID' - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type, - Authorization, and Content-Length headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - converter: func (optional) A function which will be executed on the - server's response. Often this is a function like - GDataEntryFromString which will parse the body of the server's - response and return a GDataEntry. - - Returns: - If the put succeeded, this method will return a GDataFeed, GDataEntry, - or the results of running converter on the server's result body (if - converter was specified). - """ - return GDataService.PostOrPut(self, 'PUT', data, uri, - extra_headers=extra_headers, url_params=url_params, - escape_params=escape_params, redirects_remaining=redirects_remaining, - media_source=media_source, converter=converter) - - def Delete(self, uri, extra_headers=None, url_params=None, - escape_params=True, redirects_remaining=4): - """Deletes the entry at the given URI. - - Args: - uri: string The URI of the entry to be deleted. Example: - '/base/feeds/items/ITEM-ID' - extra_headers: dict (optional) HTTP headers which are to be included. - The client automatically sets the Content-Type and - Authorization headers. - url_params: dict (optional) Additional URL parameters to be included - in the URI. These are translated into query arguments - in the form '&dict_key=value&...'. - Example: {'max-results': '250'} becomes &max-results=250 - escape_params: boolean (optional) If false, the calling code has already - ensured that the query will form a valid URL (all - reserved characters have been escaped). If true, this - method will escape the query and any URL parameters - provided. - - Returns: - True if the entry was deleted. - """ - if extra_headers is None: - extra_headers = {} - - if self.__gsessionid is not None: - if uri.find('gsessionid=') < 0: - if url_params is None: - url_params = {} - url_params['gsessionid'] = self.__gsessionid - - server_response = self.request('DELETE', uri, - headers=extra_headers, url_params=url_params) - result_body = server_response.read() - - if server_response.status == 200: - return True - elif server_response.status == 302: - if redirects_remaining > 0: - location = (server_response.getheader('Location') - or server_response.getheader('location')) - if location is not None: - m = re.compile(r'[?&]gsessionid=(\w*)').search(location) - if m is not None: - self.__gsessionid = m.group(1) - return GDataService.Delete(self, location, extra_headers, - url_params, escape_params, redirects_remaining - 1) - else: - raise RequestError({'status': server_response.status, - 'reason': '302 received without Location header', - 'body': result_body}) - else: - raise RequestError({'status': server_response.status, - 'reason': 'Redirect received, but redirects_remaining <= 0', - 'body': result_body}) - else: - raise RequestError({'status': server_response.status, - 'reason': server_response.reason, 'body': result_body}) - - -def ExtractToken(url, scopes_included_in_next=True): - """Gets the AuthSub token from the current page's URL. - - Designed to be used on the URL that the browser is sent to after the user - authorizes this application at the page given by GenerateAuthSubRequestUrl. - - Args: - url: The current page's URL. It should contain the token as a URL - parameter. Example: 'http://example.com/?...&token=abcd435' - scopes_included_in_next: If True, this function looks for a scope value - associated with the token. The scope is a URL parameter with the - key set to SCOPE_URL_PARAM_NAME. This parameter should be present - if the AuthSub request URL was generated using - GenerateAuthSubRequestUrl with include_scope_in_next set to True. - - Returns: - A tuple containing the token string and a list of scope strings for which - this token should be valid. If the scope was not included in the URL, the - tuple will contain (token, None). - """ - parsed = urllib.parse.urlparse(url) - token = gdata.auth.AuthSubTokenFromUrl(parsed[4]) - scopes = '' - if scopes_included_in_next: - for pair in parsed[4].split('&'): - if pair.startswith('%s=' % SCOPE_URL_PARAM_NAME): - scopes = urllib.parse.unquote_plus(pair.split('=')[1]) - return (token, scopes.split(' ')) - - -def GenerateAuthSubRequestUrl(next, scopes, hd='default', secure=False, - session=True, request_url='https://www.google.com/accounts/AuthSubRequest', - include_scopes_in_next=True): - """Creates a URL to request an AuthSub token to access Google services. - - For more details on AuthSub, see the documentation here: - http://code.google.com/apis/accounts/docs/AuthSub.html - - Args: - next: The URL where the browser should be sent after the user authorizes - the application. This page is responsible for receiving the token - which is embeded in the URL as a parameter. - scopes: The base URL to which access will be granted. Example: - 'http://www.google.com/calendar/feeds' will grant access to all - URLs in the Google Calendar data API. If you would like a token for - multiple scopes, pass in a list of URL strings. - hd: The domain to which the user's account belongs. This is set to the - domain name if you are using Google Apps. Example: 'example.org' - Defaults to 'default' - secure: If set to True, all requests should be signed. The default is - False. - session: If set to True, the token received by the 'next' URL can be - upgraded to a multiuse session token. If session is set to False, the - token may only be used once and cannot be upgraded. Default is True. - request_url: The base of the URL to which the user will be sent to - authorize this application to access their data. The default is - 'https://www.google.com/accounts/AuthSubRequest'. - include_scopes_in_next: Boolean if set to true, the 'next' parameter will - be modified to include the requested scope as a URL parameter. The - key for the next's scope parameter will be SCOPE_URL_PARAM_NAME. The - benefit of including the scope URL as a parameter to the next URL, is - that the page which receives the AuthSub token will be able to tell - which URLs the token grants access to. - - Returns: - A URL string to which the browser should be sent. - """ - if isinstance(scopes, list): - scope = ' '.join(scopes) - else: - scope = scopes - if include_scopes_in_next: - if next.find('?') > -1: - next += '&%s' % urllib.parse.urlencode({SCOPE_URL_PARAM_NAME:scope}) - else: - next += '?%s' % urllib.parse.urlencode({SCOPE_URL_PARAM_NAME:scope}) - return gdata.auth.GenerateAuthSubUrl(next=next, scope=scope, secure=secure, - session=session, request_url=request_url, domain=hd) - - -class Query(dict): - """Constructs a query URL to be used in GET requests - - Url parameters are created by adding key-value pairs to this object as a - dict. For example, to add &max-results=25 to the URL do - my_query['max-results'] = 25 - - Category queries are created by adding category strings to the categories - member. All items in the categories list will be concatenated with the / - symbol (symbolizing a category x AND y restriction). If you would like to OR - 2 categories, append them as one string with a | between the categories. - For example, do query.categories.append('Fritz|Laurie') to create a query - like this feed/-/Fritz%7CLaurie . This query will look for results in both - categories. - """ - - def __init__(self, feed=None, text_query=None, params=None, - categories=None): - """Constructor for Query - - Args: - feed: str (optional) The path for the feed (Examples: - '/base/feeds/snippets' or 'calendar/feeds/jo@gmail.com/private/full' - text_query: str (optional) The contents of the q query parameter. The - contents of the text_query are URL escaped upon conversion to a URI. - params: dict (optional) Parameter value string pairs which become URL - params when translated to a URI. These parameters are added to the - query's items (key-value pairs). - categories: list (optional) List of category strings which should be - included as query categories. See - http://code.google.com/apis/gdata/reference.html#Queries for - details. If you want to get results from category A or B (both - categories), specify a single list item 'A|B'. - """ - - self.feed = feed - self.categories = [] - if text_query: - self.text_query = text_query - if isinstance(params, dict): - for param in params: - self[param] = params[param] - if isinstance(categories, list): - for category in categories: - self.categories.append(category) - - def _GetTextQuery(self): - if 'q' in list(self.keys()): - return self['q'] - else: - return None - - def _SetTextQuery(self, query): - self['q'] = query - - text_query = property(_GetTextQuery, _SetTextQuery, - doc="""The feed query's q parameter""") - - def _GetAuthor(self): - if 'author' in list(self.keys()): - return self['author'] - else: - return None - - def _SetAuthor(self, query): - self['author'] = query - - author = property(_GetAuthor, _SetAuthor, - doc="""The feed query's author parameter""") - - def _GetAlt(self): - if 'alt' in list(self.keys()): - return self['alt'] - else: - return None - - def _SetAlt(self, query): - self['alt'] = query - - alt = property(_GetAlt, _SetAlt, - doc="""The feed query's alt parameter""") - - def _GetUpdatedMin(self): - if 'updated-min' in list(self.keys()): - return self['updated-min'] - else: - return None - - def _SetUpdatedMin(self, query): - self['updated-min'] = query - - updated_min = property(_GetUpdatedMin, _SetUpdatedMin, - doc="""The feed query's updated-min parameter""") - - def _GetUpdatedMax(self): - if 'updated-max' in list(self.keys()): - return self['updated-max'] - else: - return None - - def _SetUpdatedMax(self, query): - self['updated-max'] = query - - updated_max = property(_GetUpdatedMax, _SetUpdatedMax, - doc="""The feed query's updated-max parameter""") - - def _GetPublishedMin(self): - if 'published-min' in list(self.keys()): - return self['published-min'] - else: - return None - - def _SetPublishedMin(self, query): - self['published-min'] = query - - published_min = property(_GetPublishedMin, _SetPublishedMin, - doc="""The feed query's published-min parameter""") - - def _GetPublishedMax(self): - if 'published-max' in list(self.keys()): - return self['published-max'] - else: - return None - - def _SetPublishedMax(self, query): - self['published-max'] = query - - published_max = property(_GetPublishedMax, _SetPublishedMax, - doc="""The feed query's published-max parameter""") - - def _GetStartIndex(self): - if 'start-index' in list(self.keys()): - return self['start-index'] - else: - return None - - def _SetStartIndex(self, query): - if not isinstance(query, str): - query = str(query) - self['start-index'] = query - - start_index = property(_GetStartIndex, _SetStartIndex, - doc="""The feed query's start-index parameter""") - - def _GetMaxResults(self): - if 'max-results' in list(self.keys()): - return self['max-results'] - else: - return None - - def _SetMaxResults(self, query): - if not isinstance(query, str): - query = str(query) - self['max-results'] = query - - max_results = property(_GetMaxResults, _SetMaxResults, - doc="""The feed query's max-results parameter""") - - def _GetOrderBy(self): - if 'orderby' in list(self.keys()): - return self['orderby'] - else: - return None - - def _SetOrderBy(self, query): - self['orderby'] = query - - orderby = property(_GetOrderBy, _SetOrderBy, - doc="""The feed query's orderby parameter""") - - def ToUri(self): - q_feed = self.feed or '' - category_string = '/'.join( - [urllib.parse.quote_plus(c) for c in self.categories]) - # Add categories to the feed if there are any. - if len(self.categories) > 0: - q_feed = q_feed + '/-/' + category_string - return atom.service.BuildUri(q_feed, self) - - def __str__(self): - return self.ToUri() diff --git a/src/gam/util/api.py b/src/gam/util/api.py index 577b62d6..296b6f66 100644 --- a/src/gam/util/api.py +++ b/src/gam/util/api.py @@ -10,7 +10,6 @@ import os import random import re import sqlite3 -import ssl import subprocess import sys import time @@ -34,12 +33,7 @@ import httplib2 from filelock import FileLock from google.auth.jwt import Credentials as JWTCredentials -try: - import gdata.apps.audit.service - import gdata.apps.contacts.service - import gdata.service -except ImportError: - pass + from gamlib import api as API from gamlib import settings as GC @@ -534,7 +528,7 @@ def getService(api, httpObj): waitOnFailure(n, triesLimit, INVALID_JSON_RC, str(e)) continue systemErrorExit(INVALID_JSON_RC, str(e)) - except (http.client.ResponseNotReady, OSError, googleapiclient.errors.HttpError) as e: + except (http.client.HTTPException, OSError, googleapiclient.errors.HttpError) as e: errMsg = f'Connection error: {str(e) or repr(e)}' if n != triesLimit: waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg) @@ -657,27 +651,7 @@ def getSvcAcctCredentials(scopesOrAPI, userEmail, softErrors=False, forceOauth=F GM.Globals[GM.OAUTH2SERVICE_CLIENT_ID] = GM.Globals[GM.OAUTH2SERVICE_JSON_DATA]['client_id'] return credentials -def getGDataOAuthToken(gdataObj, credentials=None): - if not credentials: - credentials = getClientCredentials(refreshOnly=True) - try: - credentials.refresh(transportCreateRequest()) - except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e: - handleServerError(e) - except google.auth.exceptions.RefreshError as e: - if isinstance(e.args, tuple): - e = e.args[0] - handleOAuthTokenError(e, False) - gdataObj.additional_headers['Authorization'] = f'Bearer {credentials.token}' - if not GC.Values[GC.DOMAIN]: - GC.Values[GC.DOMAIN] = GM.Globals[GM.DECODED_ID_TOKEN].get('hd', 'UNKNOWN').lower() - if not GC.Values[GC.CUSTOMER_ID]: - GC.Values[GC.CUSTOMER_ID] = GC.MY_CUSTOMER - GM.Globals[GM.ADMIN] = GM.Globals[GM.DECODED_ID_TOKEN].get('email', 'UNKNOWN').lower() - GM.Globals[GM.OAUTH2_CLIENT_ID] = credentials.client_id - gdataObj.domain = GC.Values[GC.DOMAIN] - gdataObj.source = GAM_USER_AGENT - return True + def readDiscoveryFile(api_version): @@ -723,29 +697,6 @@ def buildGAPIObjectNoAuthentication(api): service = getService(api, httpObj) return service -def initGDataObject(gdataObj, api): - GM.Globals[GM.CURRENT_CLIENT_API] = api - credentials = getClientCredentials(noDASA=True, refreshOnly=True) - GM.Globals[GM.CURRENT_CLIENT_API_SCOPES] = API.getClientScopesSet(api).intersection(GM.Globals[GM.CREDENTIALS_SCOPES]) - if not GM.Globals[GM.CURRENT_CLIENT_API_SCOPES]: - systemErrorExit(NO_SCOPES_FOR_API_RC, Msg.NO_SCOPES_FOR_API.format(API.getAPIName(api))) - getGDataOAuthToken(gdataObj, credentials) - if GC.Values[GC.DEBUG_LEVEL] > 0: - gdataObj.debug = True - return gdataObj - -def getContactsObject(): - contactsObject = initGDataObject(gdata.apps.contacts.service.ContactsService(contactFeed=True), - API.CONTACTS) - return (GC.Values[GC.DOMAIN], contactsObject) - -def getContactsQuery(**kwargs): - if GC.Values[GC.NO_VERIFY_SSL]: - ssl._create_default_https_context = ssl._create_unverified_context - return gdata.apps.contacts.service.ContactsQuery(**kwargs) - -def getEmailAuditObject(): - return initGDataObject(gdata.apps.audit.service.AuditService(), API.EMAIL_AUDIT) # API access denied handlers (moved from access.py to break cycle) def ClientAPIAccessDeniedExit(errMsg=None): diff --git a/src/gam/util/api_call.py b/src/gam/util/api_call.py index 2eef48ae..bd7841f3 100644 --- a/src/gam/util/api_call.py +++ b/src/gam/util/api_call.py @@ -1,7 +1,7 @@ """Low-level Google API call wrappers with retry logic. -Contains callGAPI/callGAPIpages/callGData and their error-checking -helpers. Separated from api.py (which handles auth/credentials/service +Contains callGAPI/callGAPIpages and their error-checking helpers. +Separated from api.py (which handles auth/credentials/service construction) to break the api<->uid circular dependency. """ @@ -15,12 +15,12 @@ import httplib2 from gamlib import api as API from gamlib import settings as GC from gamlib import gapi as GAPI -from gamlib import gdata as GDATA + from gamlib import state as GM from gamlib import msgs as Msg from gam.var import Ent from gam.constants import GOOGLE_API_ERROR_RC, HTTP_ERROR_RC, NETWORK_ERROR_RC, SOCKET_ERROR_RC -from util.api import APIAccessDeniedExit, clearServiceCache, getGDataOAuthToken, handleOAuthTokenError, handleServerError, transportCreateRequest, waitOnFailure +from util.api import APIAccessDeniedExit, clearServiceCache, handleOAuthTokenError, handleServerError, transportCreateRequest, waitOnFailure from util.args import UTF8, formatHTTPError from util.display import FIRST_ITEM_MARKER, LAST_ITEM_MARKER, TOTAL_ITEMS_MARKER from util.fileio import checkAPICallsRate @@ -28,170 +28,6 @@ from util.output import ERROR_PREFIX, flushStderr, stderrErrorMsg, systemErrorEx HTML_TITLE_PATTERN = re.compile(r'.*(.+)') -def checkGDataError(e, service): - error = e.args - reason = error[0].get('reason', '') - body = error[0].get('body', '').decode(UTF8) - # First check for errors that need special handling - if reason in ['Token invalid - Invalid token: Stateless token expired', 'Token invalid - Invalid token: Token not found', 'gone']: - keep_domain = service.domain - getGDataOAuthToken(service) - service.domain = keep_domain - return (GDATA.TOKEN_EXPIRED, reason) - error_code = getattr(e, 'error_code', 600) - if GC.Values[GC.DEBUG_LEVEL] > 0: - writeStdout(f'{ERROR_PREFIX} {error_code}: {reason}, {body}\n') - if error_code == 600: - if (body.startswith('Quota exceeded for the current request') or - body.startswith('Quota exceeded for quota metric') or - body.startswith('Request rate higher than configured')): - return (GDATA.QUOTA_EXCEEDED, body) - if (body.startswith('Photo delete failed') or - body.startswith('Upload photo failed') or - body.startswith('Photo query failed')): - return (GDATA.NOT_FOUND, body) - if body.startswith(GDATA.API_DEPRECATED_MSG): - return (GDATA.API_DEPRECATED, body) - if reason == 'Too Many Requests': - return (GDATA.QUOTA_EXCEEDED, reason) - if reason == 'Bad Gateway': - return (GDATA.BAD_GATEWAY, reason) - if reason == 'Gateway Timeout': - return (GDATA.GATEWAY_TIMEOUT, reason) - if reason == 'Service Unavailable': - return (GDATA.SERVICE_UNAVAILABLE, reason) - if reason == 'Service disabled by G Suite admin.': - return (GDATA.FORBIDDEN, reason) - if reason == 'Internal Server Error': - return (GDATA.INTERNAL_SERVER_ERROR, reason) - if reason == 'Token invalid - Invalid token: Token disabled, revoked, or expired.': - return (GDATA.TOKEN_INVALID, 'Token disabled, revoked, or expired. Please delete and re-create oauth.txt') - if reason == 'Token invalid - AuthSub token has wrong scope': - return (GDATA.INSUFFICIENT_PERMISSIONS, reason) - if reason.startswith('Only administrators can request entries belonging to'): - return (GDATA.INSUFFICIENT_PERMISSIONS, reason) - if reason == 'You are not authorized to access this API': - return (GDATA.INSUFFICIENT_PERMISSIONS, reason) - if reason == 'Invalid domain.': - return (GDATA.INVALID_DOMAIN, reason) - if reason.startswith('You are not authorized to perform operations on the domain'): - return (GDATA.INVALID_DOMAIN, reason) - if reason == 'Bad Request': - if 'already exists' in body: - return (GDATA.ENTITY_EXISTS, Msg.DUPLICATE) - return (GDATA.BAD_REQUEST, body) - if reason == 'Forbidden': - return (GDATA.FORBIDDEN, body) - if reason == 'Not Found': - return (GDATA.NOT_FOUND, Msg.DOES_NOT_EXIST) - if reason == 'Not Implemented': - return (GDATA.NOT_IMPLEMENTED, body) - if reason == 'Precondition Failed': - return (GDATA.PRECONDITION_FAILED, reason) - elif error_code == 602: - if body.startswith(GDATA.API_DEPRECATED_MSG): - return (GDATA.API_DEPRECATED, body) - if reason == 'Bad Request': - return (GDATA.BAD_REQUEST, body) - elif error_code == 610: - if reason == 'Service disabled by G Suite admin.': - return (GDATA.FORBIDDEN, reason) - - # We got a "normal" error, define the mapping below - error_code_map = { - 1000: reason, - 1001: reason, - 1002: 'Unauthorized and forbidden', - 1100: 'User deleted recently', - 1200: 'Domain user limit exceeded', - 1201: 'Domain alias limit exceeded', - 1202: 'Domain suspended', - 1203: 'Domain feature unavailable', - 1300: f'Entity {getattr(e, "invalidInput", "")} exists', - 1301: f'Entity {getattr(e, "invalidInput", "")} Does Not Exist', - 1302: 'Entity Name Is Reserved', - 1303: f'Entity {getattr(e, "invalidInput", "")} name not valid', - 1306: f'{getattr(e, "invalidInput", "")} has members. Cannot delete.', - 1317: f'Invalid input {getattr(e, "invalidInput", "")}, reason {getattr(e, "reason", "")}', - 1400: 'Invalid Given Name', - 1401: 'Invalid Family Name', - 1402: 'Invalid Password', - 1403: 'Invalid Username', - 1404: 'Invalid Hash Function Name', - 1405: 'Invalid Hash Digest Length', - 1406: 'Invalid Email Address', - 1407: 'Invalid Query Parameter Value', - 1408: 'Invalid SSO Signing Key', - 1409: 'Invalid Encryption Public Key', - 1410: 'Feature Unavailable For User', - 1411: 'Invalid Encryption Public Key Format', - 1500: 'Too Many Recipients On Email List', - 1501: 'Too Many Aliases For User', - 1502: 'Too Many Delegates For User', - 1601: 'Duplicate Destinations', - 1602: 'Too Many Destinations', - 1603: 'Invalid Route Address', - 1700: 'Group Cannot Contain Cycle', - 1800: 'Group Cannot Contain Cycle', - 1801: f'Invalid value {getattr(e, "invalidInput", "")}', - } - return (error_code, error_code_map.get(error_code, f'Unknown Error: {str(e)}')) - -def callGData(service, function, - bailOnInternalServerError=False, softErrors=False, - throwErrors=None, retryErrors=None, triesLimit=0, - **kwargs): - if throwErrors is None: - throwErrors = [] - if retryErrors is None: - retryErrors = [] - if triesLimit == 0: - triesLimit = GC.Values[GC.API_CALLS_TRIES_LIMIT] - allRetryErrors = GDATA.NON_TERMINATING_ERRORS+retryErrors - method = getattr(service, function) - if GC.Values[GC.API_CALLS_RATE_CHECK]: - checkAPICallsRate() - for n in range(1, triesLimit+1): - try: - return method(**kwargs) - except (gdata.service.RequestError, gdata.apps.service.AppsForYourDomainException) as e: - error_code, error_message = checkGDataError(e, service) - if (n != triesLimit) and (error_code in allRetryErrors): - if (error_code == GDATA.INTERNAL_SERVER_ERROR and - bailOnInternalServerError and n == GC.Values[GC.BAIL_ON_INTERNAL_ERROR_TRIES]): - raise GDATA.ERROR_CODE_EXCEPTION_MAP[error_code](error_message) - waitOnFailure(n, triesLimit, error_code, error_message) - continue - if error_code in throwErrors: - if error_code in GDATA.ERROR_CODE_EXCEPTION_MAP: - raise GDATA.ERROR_CODE_EXCEPTION_MAP[error_code](error_message) - raise - if softErrors: - stderrErrorMsg(f'{error_code} - {error_message}{["", ": Giving up."][n > 1]}') - return None - if error_code == GDATA.INSUFFICIENT_PERMISSIONS: - APIAccessDeniedExit() - systemErrorExit(GOOGLE_API_ERROR_RC, f'{error_code} - {error_message}') - except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e: - if n != triesLimit: - waitOnFailure(n, triesLimit, NETWORK_ERROR_RC, str(e)) - continue - handleServerError(e) - except google.auth.exceptions.RefreshError as e: - if isinstance(e.args, tuple): - e = e.args[0] - handleOAuthTokenError(e, GDATA.SERVICE_NOT_APPLICABLE in throwErrors) - raise GDATA.ERROR_CODE_EXCEPTION_MAP[GDATA.SERVICE_NOT_APPLICABLE](str(e)) - except (http.client.ResponseNotReady, OSError) as e: - errMsg = f'Connection error: {str(e) or repr(e)}' - if n != triesLimit: - waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg) - continue - if softErrors: - writeStderr(f'\n{ERROR_PREFIX}{errMsg} - Giving up.\n') - return None - systemErrorExit(SOCKET_ERROR_RC, errMsg) - def writeGotMessage(msg): if GC.Values[GC.SHOW_GETTINGS_GOT_NL]: writeStderr(msg) @@ -205,45 +41,6 @@ def writeGotMessage(msg): GM.Globals[GM.LAST_GOT_MSG_LEN] = msgLen flushStderr() -def callGDataPages(service, function, - pageMessage=None, - softErrors=False, throwErrors=None, retryErrors=None, - uri=None, - **kwargs): - if throwErrors is None: - throwErrors = [] - if retryErrors is None: - retryErrors = [] - nextLink = None - allResults = [] - totalItems = 0 - while True: - this_page = callGData(service, function, - softErrors=softErrors, throwErrors=throwErrors, retryErrors=retryErrors, - uri=uri, - **kwargs) - if this_page: - nextLink = this_page.GetNextLink() - pageItems = len(this_page.entry) - if pageItems == 0: - nextLink = None - totalItems += pageItems - allResults.extend(this_page.entry) - else: - nextLink = None - pageItems = 0 - if pageMessage: - show_message = pageMessage.replace(TOTAL_ITEMS_MARKER, str(totalItems)) - writeGotMessage(show_message.format(Ent.ChooseGetting(totalItems))) - if nextLink is None: - if pageMessage and (pageMessage[-1] != '\n'): - writeStderr('\r\n') - flushStderr() - return allResults - uri = nextLink.href - if 'url_params' in kwargs: - kwargs['url_params'].pop('start-index', None) - def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True): def makeErrorDict(code, reason, message): return {'error': {'code': code, 'errors': [{'reason': reason, 'message': message}]}} diff --git a/src/gam/util/connection.py b/src/gam/util/connection.py index c28f525d..4ce4b08d 100644 --- a/src/gam/util/connection.py +++ b/src/gam/util/connection.py @@ -262,8 +262,6 @@ def doCheckConnection(): # to ensure we are checking all hosts GAM may use we should # keep this. for api in API._INFO: - if api in [API.CONTACTS, API.EMAIL_AUDIT]: - continue svc = getService(api, httpObj) base_url = svc._rootDesc.get('baseUrl') parsed_base_url = urlparse(base_url)