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)