huge dump for Classroom, CloudPrint and batch fixes/improvements

This commit is contained in:
Jay Lee
2015-07-02 05:36:37 -04:00
parent 0b035deff0
commit 277b5ac261
9 changed files with 1435 additions and 67 deletions

View File

@ -10,6 +10,7 @@ xcopy LICENSE gam\
xcopy whatsnew.txt gam\ xcopy whatsnew.txt gam\
xcopy cacert.pem gam\ xcopy cacert.pem gam\
xcopy admin-settings-v1.json gam\ xcopy admin-settings-v1.json gam\
xcopy cloudprint-v2.json gam\
del gam\w9xpopen.exe del gam\w9xpopen.exe
"%ProgramFiles(x86)%\7-Zip\7z.exe" a -tzip gam-%1-windows.zip gam\ -xr!.svn "%ProgramFiles(x86)%\7-Zip\7z.exe" a -tzip gam-%1-windows.zip gam\ -xr!.svn
@ -18,4 +19,5 @@ xcopy LICENSE gam-64\
xcopy whatsnew.txt gam-64\ xcopy whatsnew.txt gam-64\
xcopy cacert.pem gam-64\ xcopy cacert.pem gam-64\
xcopy admin-settings-v1.json gam-64\ xcopy admin-settings-v1.json gam-64\
xcopy cloudprint-v2.json gam-64\
"%ProgramFiles(x86)%\7-Zip\7z.exe" a -tzip gam-%1-windows-x64.zip gam-64\ -xr!.svn "%ProgramFiles(x86)%\7-Zip\7z.exe" a -tzip gam-%1-windows-x64.zip gam-64\ -xr!.svn

486
cloudprint-v2.json Normal file
View File

@ -0,0 +1,486 @@
{
"kind": "discovery#restDescription",
"discoveryVersion": "v1",
"id": "cloudprint:v2",
"name": "cloudprint",
"version": "v2",
"revision": "20150605",
"title": "Cloud Print API",
"description": "Lets you access Cloud Print Printers",
"ownerDomain": "google.com",
"ownerName": "Google",
"icons": {
"x16": "http://www.google.com/images/icons/product/search-16.gif",
"x32": "http://www.google.com/images/icons/product/search-32.gif"
},
"documentationLink": "https://developers.google.com/cloud-print",
"protocol": "rest",
"baseUrl": "https://www.google.com/",
"basePath": "/cloudprint/",
"rootUrl": "https://www.google.com/",
"servicePath": "/cloudprint/",
"parameters": {
"prettyPrint": {
"type": "boolean",
"description": "Returns response with indentations and line breaks.",
"default": "true",
"location": "query"
}
},
"auth": {
"oauth2": {
"scopes": {
"https://www.googleapis.com/auth/cloudprint": {
"description": "Manage Cloud Print"
}
}
}
},
"schemas": {
"Job": {
"id": "Job",
"type": "object",
"description": "Job Object",
"properties": {
"title": {
"type": "string",
"description": "Job Title"
},
"id": {
"type": "string",
"description": "Unique ID"
}
}
},
"Jobs": {
"id": "Jobs",
"type": "object",
"description": "List of Jobs.",
"properties": {
"jobs": {
"type": "array",
"description": "List of job objects.",
"items": {
"$ref": "Job"
}
}
}
},
"Printer": {
"id": "Printer",
"type": "object",
"description": "Printer Object",
"properties": {
"displayName": {
"type": "string",
"description": "Display Name"
},
"id": {
"type": "string",
"description": "Unique ID"
}
}
},
"Printers": {
"id": "Printers",
"type": "object",
"description": "List of Printers.",
"properties": {
"printers": {
"type": "array",
"description": "List of printer objects.",
"items": {
"$ref": "Printer"
}
}
}
}
},
"resources": {
"jobs": {
"methods": {
"delete": {
"id": "cloudprint.jobs.delete",
"path": "deletejob",
"httpMethod": "GET",
"parameters": {
"jobid": {
"type": "string",
"location": "query",
"required": "true"
}
}
},
"fetch": {
"id": "cloudprint.jobs.fetch",
"path": "fetch",
"httpMethod": "GET",
"parameters": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
}
},
"response": {
"$ref": "Jobs"
}
},
"getticket": {
"id": "cloudprint.jobs.getticket",
"path": "ticket",
"httpMethod": "GET",
"parameters": {
"jobid": {
"type": "string",
"required": "true",
"location": "query"
},
"use_cjt": {
"type": "boolean",
"required": "true",
"location": "query"
}
}
},
"list": {
"id": "cloudprint.jobs.list",
"path": "jobs",
"httpMethod": "GET",
"parameters": {
"printerid": {
"type": "string",
"location": "query"
},
"owner": {
"type": "string",
"location": "query"
},
"status": {
"type": "string",
"location": "query"
},
"q": {
"type": "string",
"location": "query"
},
"offset": {
"type": "string",
"location": "query"
},
"limit": {
"type": "string",
"location": "query"
},
"sortorder": {
"type": "string",
"location": "query"
}
},
"response": {
"$ref": "Jobs"
}
},
"update": {
"id": "cloudprint.jobs.update",
"path": "control",
"httpMethod": "GET",
"parameters": {
"jobid": {
"type": "string",
"required": "true",
"location": "query"
},
"semantic_state_diff": {
"type": "string",
"required": "true",
"location": "query"
}
},
"response": {
"$ref": "Jobs"
}
},
"resubmit": {
"id": "cloudprint.jobs.resubmit",
"path": "resubmit",
"httpMethod": "POST",
"description": "resubmit a job to new printer.",
"parameters": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
},
"jobid": {
"type": "string",
"required": "true",
"location": "query"
},
"ticket": {
"type": "string",
"location": "query"
}
},
"response": {
"$ref": "Job"
}
},
"submit": {
"id": "cloudprint.jobs.submit",
"path": "submit",
"httpMethod": "POST",
"description": "Send a print job to cloud print.",
"request": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
},
"title": {
"type": "string",
"location": "query"
},
"ticket": {
"type": "string",
"location": "query"
},
"content": {
"type": "string",
"location": "query"
},
"contentType": {
"type": "string",
"location": "query"
},
"tag": {
"type": "string",
"location": "query"
}
},
"response": {
"$ref": "Job"
}
}
}
},
"printers": {
"methods": {
"get": {
"id": "cloudprint.printers.get",
"path": "printer",
"httpMethod": "GET",
"parameters": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
},
"extra_fields": {
"type": "string",
"location": "query"
}
},
"response": {
"$ref": "Printer"
}
},
"list": {
"id": "cloudprint.printers.list",
"path": "search",
"httpMethod": "GET",
"description": "List all printers",
"parameters": {
"q": {
"type": "string",
"description": "Query list of printers",
"location": "query"
},
"type": {
"type": "string",
"description": "limit results to printers of type",
"location": "query"
},
"connection_status": {
"type": "string",
"description": "limit results to printers with this status",
"location": "query"
},
"extra_fields": {
"type": "string",
"description": "include extra fields",
"location": "query"
}
},
"response": {
"$ref": "Printers"
}
},
"share": {
"id": "cloudprint.printers.share",
"path": "share",
"httpMethod": "GET",
"description": "Share printer with user, group or domain",
"parameters": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
},
"scope": {
"type": "string",
"location": "query"
},
"role": {
"type": "string",
"location": "query"
},
"type": {
"type": "string",
"location": "query"
},
"skip_notification": {
"type": "boolean",
"location": "query"
},
"public": {
"type": "boolean",
"location": "query"
}
}
},
"unshare": {
"id": "cloudprint.printers.unshare",
"path": "unshare",
"httpMethod": "GET",
"description": "unshare printer with user, group or domain",
"parameters": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
},
"scope": {
"type": "string",
"location": "query"
},
"public": {
"type": "string",
"location": "query"
}
}
},
"delete": {
"id": "cloudprint.printers.delete",
"path": "delete",
"httpMethod": "GET",
"description": "delete a printer",
"parameters": {
"printerid": {
"type": "string",
"required": "true",
"location": "query"
}
}
},
"update": {
"id": "cloudprint.printers.update",
"path": "update",
"httpMethod": "GET",
"description": "update a printer",
"parameters": {
"isTosAccepted": {
"type": "boolean",
"location": "query"
},
"gcpVersion": {
"type": "string",
"location": "query"
},
"setupUrl": {
"type": "string",
"location": "query"
},
"supportUrl": {
"type": "string",
"location": "query"
},
"firmware": {
"type": "string",
"location": "query"
},
"currentQuota": {
"type": "string",
"location": "query"
},
"type": {
"type": "string",
"location": "query"
},
"public": {
"type": "boolean",
"location": "query"
},
"status": {
"type": "string",
"location": "query"
},
"proxy": {
"type": "string",
"location": "query"
},
"manufacturer": {
"type": "string",
"location": "query"
},
"defaultDisplayName": {
"type": "string",
"location": "query"
},
"displayName": {
"type": "string",
"location": "query"
},
"name": {
"type": "string",
"location": "query"
},
"uuid": {
"type": "string",
"location": "query"
},
"updateUrl": {
"type": "string",
"location": "query"
},
"ownerId": {
"type": "string",
"location": "query"
},
"model": {
"type": "string",
"location": "query"
},
"description": {
"type": "string",
"location": "query"
},
"printerid": {
"type": "string",
"required": "true",
"location": "query"
},
"quotaEnabled": {
"type": "boolean",
"location": "query"
},
"dailyQuota": {
"type": "string",
"location": "query"
}
}
}
}
}
}
}

912
gam.py

File diff suppressed because it is too large Load Diff

View File

@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
__version__ = "1.4.0" __version__ = "1.4.1"

View File

@ -56,6 +56,7 @@ from googleapiclient.errors import MediaUploadSizeError
from googleapiclient.errors import UnacceptableMimeTypeError from googleapiclient.errors import UnacceptableMimeTypeError
from googleapiclient.errors import UnknownApiNameOrVersion from googleapiclient.errors import UnknownApiNameOrVersion
from googleapiclient.errors import UnknownFileType from googleapiclient.errors import UnknownFileType
from googleapiclient.http import BatchHttpRequest
from googleapiclient.http import HttpRequest from googleapiclient.http import HttpRequest
from googleapiclient.http import MediaFileUpload from googleapiclient.http import MediaFileUpload
from googleapiclient.http import MediaUpload from googleapiclient.http import MediaUpload
@ -950,6 +951,27 @@ class Resource(object):
self._add_next_methods(self._resourceDesc, self._schema) self._add_next_methods(self._resourceDesc, self._schema)
def _add_basic_methods(self, resourceDesc, rootDesc, schema): def _add_basic_methods(self, resourceDesc, rootDesc, schema):
# If this is the root Resource, add a new_batch_http_request() method.
if resourceDesc == rootDesc:
batch_uri = '%s%s' % (
rootDesc['rootUrl'], rootDesc.get('batchPath', 'batch'))
def new_batch_http_request(callback=None):
"""Create a BatchHttpRequest object based on the discovery document.
Args:
callback: callable, A callback to be called for each response, of the
form callback(id, response, exception). The first parameter is the
request id, and the second is the deserialized response object. The
third is an apiclient.errors.HttpError exception object if an HTTP
error occurred while processing the request, or None if no error
occurred.
Returns:
A BatchHttpRequest object based on the discovery document.
"""
return BatchHttpRequest(callback=callback, batch_uri=batch_uri)
self._set_dynamic_attr('new_batch_http_request', new_batch_http_request)
# Add basic methods to Resource # Add basic methods to Resource
if 'methods' in resourceDesc: if 'methods' in resourceDesc:
for methodName, methodDesc in six.iteritems(resourceDesc['methods']): for methodName, methodDesc in six.iteritems(resourceDesc['methods']):

View File

@ -37,6 +37,8 @@ class HttpError(Error):
@util.positional(3) @util.positional(3)
def __init__(self, resp, content, uri=None): def __init__(self, resp, content, uri=None):
self.resp = resp self.resp = resp
if not isinstance(content, bytes):
raise TypeError("HTTP content should be bytes")
self.content = content self.content = content
self.uri = uri self.uri = uri
@ -44,7 +46,7 @@ class HttpError(Error):
"""Calculate the reason for the error from the response content.""" """Calculate the reason for the error from the response content."""
reason = self.resp.reason reason = self.resp.reason
try: try:
data = json.loads(self.content) data = json.loads(self.content.decode('utf-8'))
reason = data['error']['message'] reason = data['error']['message']
except (ValueError, KeyError): except (ValueError, KeyError):
pass pass

View File

@ -1120,10 +1120,6 @@ class BatchHttpRequest(object):
g.flatten(msg, unixfrom=False) g.flatten(msg, unixfrom=False)
body = fp.getvalue() body = fp.getvalue()
# Strip off the \n\n that the MIME lib tacks onto the end of the payload.
if request.body is None:
body = body[:-2]
return status_line + body return status_line + body
def _deserialize_response(self, payload): def _deserialize_response(self, payload):
@ -1252,11 +1248,12 @@ class BatchHttpRequest(object):
if resp.status >= 300: if resp.status >= 300:
raise HttpError(resp, content, uri=self._batch_uri) raise HttpError(resp, content, uri=self._batch_uri)
# Now break out the individual responses and store each one.
boundary, _ = content.split(None, 1)
# Prepend with a content-type header so FeedParser can handle it. # Prepend with a content-type header so FeedParser can handle it.
header = 'content-type: %s\r\n\r\n' % resp['content-type'] header = 'content-type: %s\r\n\r\n' % resp['content-type']
# PY3's FeedParser only accepts unicode. So we should decode content
# here, and encode each payload again.
if six.PY3:
content = content.decode('utf-8')
for_parser = header + content for_parser = header + content
parser = FeedParser() parser = FeedParser()
@ -1270,6 +1267,9 @@ class BatchHttpRequest(object):
for part in mime_response.get_payload(): for part in mime_response.get_payload():
request_id = self._header_to_id(part['Content-ID']) request_id = self._header_to_id(part['Content-ID'])
response, content = self._deserialize_response(part.get_payload()) response, content = self._deserialize_response(part.get_payload())
# We encode content here to emulate normal http response.
if isinstance(content, six.text_type):
content = content.encode('utf-8')
self._responses[request_id] = (response, content) self._responses[request_id] = (response, content)
@util.positional(1) @util.positional(1)
@ -1458,7 +1458,7 @@ class HttpMock(object):
headers: dict, header to return with response headers: dict, header to return with response
""" """
if headers is None: if headers is None:
headers = {'status': '200 OK'} headers = {'status': '200'}
if filename: if filename:
f = open(filename, 'r') f = open(filename, 'r')
self.data = f.read() self.data = f.read()
@ -1536,6 +1536,8 @@ class HttpMockSequence(object):
content = body content = body
elif content == 'echo_request_uri': elif content == 'echo_request_uri':
content = uri content = uri
if isinstance(content, six.text_type):
content = content.encode('utf-8')
return httplib2.Response(resp), content return httplib2.Response(resp), content

View File

@ -22,7 +22,7 @@ __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)",
"Sam Ruby", "Sam Ruby",
"Louis Nyffenegger"] "Louis Nyffenegger"]
__license__ = "MIT" __license__ = "MIT"
__version__ = "0.9" __version__ = "0.9.1"
import re import re
import sys import sys
@ -255,8 +255,8 @@ def safename(filename):
filename = re_slash.sub(",", filename) filename = re_slash.sub(",", filename)
# limit length of filename # limit length of filename
if len(filename)>64: if len(filename)>200:
filename=filename[:64] filename=filename[:200]
return ",".join((filename, filemd5)) return ",".join((filename, filemd5))
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+') NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
@ -749,12 +749,27 @@ class ProxyInfo(object):
bypass_hosts = () bypass_hosts = ()
def __init__(self, proxy_type, proxy_host, proxy_port, def __init__(self, proxy_type, proxy_host, proxy_port,
proxy_rdns=None, proxy_user=None, proxy_pass=None): proxy_rdns=True, proxy_user=None, proxy_pass=None):
"""The parameter proxy_type must be set to one of socks.PROXY_TYPE_XXX """
constants. For example: Args:
proxy_type: The type of proxy server. This must be set to one of
socks.PROXY_TYPE_XXX constants. For example:
p = ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, p = ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP,
proxy_host='localhost', proxy_port=8000) proxy_host='localhost', proxy_port=8000)
proxy_host: The hostname or IP address of the proxy server.
proxy_port: The port that the proxy server is running on.
proxy_rdns: If True (default), DNS queries will not be performed
locally, and instead, handed to the proxy to resolve. This is useful
if the network does not allow resolution of non-local names. In
httplib2 0.9 and earlier, this defaulted to False.
proxy_user: The username used to authenticate with the proxy server.
proxy_pass: The password used to authenticate with the proxy server.
""" """
self.proxy_type = proxy_type self.proxy_type = proxy_type
self.proxy_host = proxy_host self.proxy_host = proxy_host
@ -871,12 +886,12 @@ class HTTPConnectionWithTimeout(httplib.HTTPConnection):
if self.proxy_info and self.proxy_info.isgood(): if self.proxy_info and self.proxy_info.isgood():
use_proxy = True use_proxy = True
proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple() proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple()
else:
use_proxy = False
if use_proxy and proxy_rdns:
host = proxy_host host = proxy_host
port = proxy_port port = proxy_port
else: else:
use_proxy = False
host = self.host host = self.host
port = self.port port = self.port
@ -993,12 +1008,12 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
if self.proxy_info and self.proxy_info.isgood(): if self.proxy_info and self.proxy_info.isgood():
use_proxy = True use_proxy = True
proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple() proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass = self.proxy_info.astuple()
else:
use_proxy = False
if use_proxy and proxy_rdns:
host = proxy_host host = proxy_host
port = proxy_port port = proxy_port
else: else:
use_proxy = False
host = self.host host = self.host
port = self.port port = self.port
@ -1481,7 +1496,7 @@ class Http(object):
info = email.Message.Message() info = email.Message.Message()
cached_value = None cached_value = None
if self.cache: if self.cache:
cachekey = defrag_uri cachekey = defrag_uri.encode('utf-8')
cached_value = self.cache.get(cachekey) cached_value = self.cache.get(cachekey)
if cached_value: if cached_value:
# info = email.message_from_string(cached_value) # info = email.message_from_string(cached_value)

View File

@ -1,3 +1,14 @@
GAM 3.5
-Support for the new Google Classroom API.
-create, update, info and delete courses
-add, remove and sync course teachers and students
-print courses and course participants
-Google CloudPrint API Support
-update, info, delete and report printers
-share, unshare and get ACLs for printers
-submit, cancel, report and delete print jobs
-Bug fixes and improvements to GAM batch commands
GAM 3.45 GAM 3.45
-add six.py to solve compatability issues on OS X and Linux -add six.py to solve compatability issues on OS X and Linux
-be conservative with password hashing to prevent timeouts -be conservative with password hashing to prevent timeouts