Compare commits

...

37 Commits

Author SHA1 Message Date
Jay Lee
11bac44de6 3.43 whatsnew 2015-03-18 14:30:21 -04:00
Jay Lee
ee35a41d03 Revert "look for extra-args.txt file to read additional GAPI arguments from."
This reverts commit 6b2aa1c532.
2015-03-18 14:13:44 -04:00
Jay Lee
baf2e67744 Version 3.43 2015-03-18 13:56:14 -04:00
Jay Lee
0542a09b88 Have short URL use a key and catch any errors 2015-03-18 13:51:20 -04:00
Your Name
e37e6935c4 One liner fixes for calendar add/delete ACLs 2015-03-04 15:07:23 -05:00
Your Name
cbe848faff Merge branch 'master' of github.com:jay0lee/GAM 2015-03-04 14:59:20 -05:00
Your Name
6b2aa1c532 look for extra-args.txt file to read additional GAPI arguments from. 2015-03-04 14:58:46 -05:00
Jay Lee
9939fa0198 Merge pull request #24 from daethnir/set-unix-execute-bit
Adds execute bit to gam for unix/linux.
2014-12-03 08:57:02 -05:00
Bri Hatch
518b820ad5 Adds execute bit to gam for unix/linux. 2014-12-02 21:39:29 -08:00
Jay Lee
fd9a6b6737 fix permissionId handling on drive ACL delete/update 2014-11-21 08:40:00 -05:00
Jay Lee
3e8bb878c8 catch unauthorized client and show service account instructions 2014-11-21 08:39:27 -05:00
Jay Lee
25cd11e3a9 update whatsnew.txt for 3.42 2014-11-19 15:02:09 -05:00
Jay Lee
6e7c15d101 version bump to 3.42 2014-11-19 14:42:06 -05:00
Jay Lee
bc48432a8d return nice error if oauth2service.json missing 2014-11-19 12:16:04 -05:00
Jay Lee
723e8c042a get all groups for users with >200 2014-11-19 12:07:43 -05:00
Jay Lee
995f4db93b import string 2014-11-19 11:53:05 -05:00
Jay Lee
ac401bd1f2 remove oauth2client/anyjson.py 2014-11-19 11:50:17 -05:00
Jay Lee
9e2198e115 convert to string before urldecode 2014-11-19 11:49:46 -05:00
Jay Lee
eaf99c682f fix commit-batch on batch commands 2014-11-19 11:14:30 -05:00
Jay Lee
447a807f69 'gam license <sku>' to perform actions for users with given skus 2014-11-19 11:12:41 -05:00
Jay Lee
a88dde7d7a one more Code->GitHub URL 2014-11-19 11:06:19 -05:00
Jay Lee
2e28793663 gam print users ismailboxsetup 2014-11-19 11:05:56 -05:00
Jay Lee
dd90f6c0ad simplify cros/mobile info commands 2014-11-19 10:31:46 -05:00
Jay Lee
9c67b6b8b4 show gmailprofile command 2014-11-19 10:24:04 -05:00
Jay Lee
206b6864c7 user activity cleanup 2014-11-19 10:10:51 -05:00
Jay Lee
d65a2494bf remove stale comments 2014-11-19 10:07:58 -05:00
Jay Lee
d806d55a64 more consistency on user groupings ('all users', etc) 2014-11-19 10:06:32 -05:00
Jay Lee
05f5ed338b update all Google Code URLs to GitHub 2014-11-19 09:38:43 -05:00
Jay Lee
a119f77237 remove simplejson 2014-11-19 09:30:30 -05:00
Jay Lee
227985e8eb cleanup old apiclient files 2014-11-19 09:24:01 -05:00
Jay Lee
0ca14a918b upgrade googleapiclient and oauth2client versions 2014-11-19 09:22:13 -05:00
Jay Lee
71ade81064 no more simplejson 2014-11-19 09:13:25 -05:00
Jay Lee
c6012f049a 'gam <users> update labels' cleanup 2014-11-19 09:09:33 -05:00
Jay Lee
d05eab9f3f cleanup 2014-11-19 08:54:02 -05:00
Jay Lee
09816fa817 show more info in gam info cros 2014-11-19 08:53:19 -05:00
Jay Lee
3da941d8b4 gam mobile <id> action accountwipe 2014-11-19 08:52:01 -05:00
Jay Lee
8f2bc384bd OAuth Tokens Report 2014-11-19 08:48:29 -05:00
62 changed files with 1413 additions and 7595 deletions

View File

@@ -1,274 +0,0 @@
# 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.
"""Push notifications support.
This code is based on experimental APIs and is subject to change.
"""
__author__ = 'afshar@google.com (Ali Afshar)'
import binascii
import collections
import os
import urllib
SUBSCRIBE = 'X-GOOG-SUBSCRIBE'
SUBSCRIPTION_ID = 'X-GOOG-SUBSCRIPTION-ID'
TOPIC_ID = 'X-GOOG-TOPIC-ID'
TOPIC_URI = 'X-GOOG-TOPIC-URI'
CLIENT_TOKEN = 'X-GOOG-CLIENT-TOKEN'
EVENT_TYPE = 'X-GOOG-EVENT-TYPE'
UNSUBSCRIBE = 'X-GOOG-UNSUBSCRIBE'
class InvalidSubscriptionRequestError(ValueError):
"""The request cannot be subscribed."""
def new_token():
"""Gets a random token for use as a client_token in push notifications.
Returns:
str, a new random token.
"""
return binascii.hexlify(os.urandom(32))
class Channel(object):
"""Base class for channel types."""
def __init__(self, channel_type, channel_args):
"""Create a new Channel.
You probably won't need to create this channel manually, since there are
subclassed Channel for each specific type with a more customized set of
arguments to pass. However, you may wish to just create it manually here.
Args:
channel_type: str, the type of channel.
channel_args: dict, arguments to pass to the channel.
"""
self.channel_type = channel_type
self.channel_args = channel_args
def as_header_value(self):
"""Create the appropriate header for this channel.
Returns:
str encoded channel description suitable for use as a header.
"""
return '%s?%s' % (self.channel_type, urllib.urlencode(self.channel_args))
def write_header(self, headers):
"""Write the appropriate subscribe header to a headers dict.
Args:
headers: dict, headers to add subscribe header to.
"""
headers[SUBSCRIBE] = self.as_header_value()
class WebhookChannel(Channel):
"""Channel for registering web hook notifications."""
def __init__(self, url, app_engine=False):
"""Create a new WebhookChannel
Args:
url: str, URL to post notifications to.
app_engine: bool, default=False, whether the destination for the
notifications is an App Engine application.
"""
super(WebhookChannel, self).__init__(
channel_type='web_hook',
channel_args={
'url': url,
'app_engine': app_engine and 'true' or 'false',
}
)
class Headers(collections.defaultdict):
"""Headers for managing subscriptions."""
ALL_HEADERS = set([SUBSCRIBE, SUBSCRIPTION_ID, TOPIC_ID, TOPIC_URI,
CLIENT_TOKEN, EVENT_TYPE, UNSUBSCRIBE])
def __init__(self):
"""Create a new subscription configuration instance."""
collections.defaultdict.__init__(self, str)
def __setitem__(self, key, value):
"""Set a header value, ensuring the key is an allowed value.
Args:
key: str, the header key.
value: str, the header value.
Raises:
ValueError if key is not one of the accepted headers.
"""
normal_key = self._normalize_key(key)
if normal_key not in self.ALL_HEADERS:
raise ValueError('Header name must be one of %s.' % self.ALL_HEADERS)
else:
return collections.defaultdict.__setitem__(self, normal_key, value)
def __getitem__(self, key):
"""Get a header value, normalizing the key case.
Args:
key: str, the header key.
Returns:
String header value.
Raises:
KeyError if the key is not one of the accepted headers.
"""
normal_key = self._normalize_key(key)
if normal_key not in self.ALL_HEADERS:
raise ValueError('Header name must be one of %s.' % self.ALL_HEADERS)
else:
return collections.defaultdict.__getitem__(self, normal_key)
def _normalize_key(self, key):
"""Normalize a header name for use as a key."""
return key.upper()
def items(self):
"""Generator for each header."""
for header in self.ALL_HEADERS:
value = self[header]
if value:
yield header, value
def write(self, headers):
"""Applies the subscription headers.
Args:
headers: dict of headers to insert values into.
"""
for header, value in self.items():
headers[header.lower()] = value
def read(self, headers):
"""Read from headers.
Args:
headers: dict of headers to read from.
"""
for header in self.ALL_HEADERS:
if header.lower() in headers:
self[header] = headers[header.lower()]
class Subscription(object):
"""Information about a subscription."""
def __init__(self):
"""Create a new Subscription."""
self.headers = Headers()
@classmethod
def for_request(cls, request, channel, client_token=None):
"""Creates a subscription and attaches it to a request.
Args:
request: An http.HttpRequest to modify for making a subscription.
channel: A apiclient.push.Channel describing the subscription to
create.
client_token: (optional) client token to verify the notification.
Returns:
New subscription object.
"""
subscription = cls.for_channel(channel=channel, client_token=client_token)
subscription.headers.write(request.headers)
if request.method != 'GET':
raise InvalidSubscriptionRequestError(
'Can only subscribe to requests which are GET.')
request.method = 'POST'
def _on_response(response, subscription=subscription):
"""Called with the response headers. Reads the subscription headers."""
subscription.headers.read(response)
request.add_response_callback(_on_response)
return subscription
@classmethod
def for_channel(cls, channel, client_token=None):
"""Alternate constructor to create a subscription from a channel.
Args:
channel: A apiclient.push.Channel describing the subscription to
create.
client_token: (optional) client token to verify the notification.
Returns:
New subscription object.
"""
subscription = cls()
channel.write_header(subscription.headers)
if client_token is None:
client_token = new_token()
subscription.headers[SUBSCRIPTION_ID] = new_token()
subscription.headers[CLIENT_TOKEN] = client_token
return subscription
def verify(self, headers):
"""Verifies that a webhook notification has the correct client_token.
Args:
headers: dict of request headers for a push notification.
Returns:
Boolean value indicating whether the notification is verified.
"""
new_subscription = Subscription()
new_subscription.headers.read(headers)
return new_subscription.client_token == self.client_token
@property
def subscribe(self):
"""Subscribe header value."""
return self.headers[SUBSCRIBE]
@property
def subscription_id(self):
"""Subscription ID header value."""
return self.headers[SUBSCRIPTION_ID]
@property
def topic_id(self):
"""Topic ID header value."""
return self.headers[TOPIC_ID]
@property
def topic_uri(self):
"""Topic URI header value."""
return self.headers[TOPIC_URI]
@property
def client_token(self):
"""Client Token header value."""
return self.headers[CLIENT_TOKEN]
@property
def event_type(self):
"""Event Type header value."""
return self.headers[EVENT_TYPE]
@property
def unsubscribe(self):
"""Unsuscribe header value."""
return self.headers[UNSUBSCRIBE]

320
gam.py Normal file → Executable file
View File

@@ -19,35 +19,35 @@
u"""Dito GAM is a command line tool which allows Administrators to control their Google Apps domain and accounts.
With GAM you can programatically create users, turn on/off services for users like POP and Forwarding and much more.
For more information, see http://code.google.com/p/google-apps-manager
For more information, see http://git.io/gam
"""
__author__ = u'Jay Lee <jay0lee@gmail.com>'
__version__ = u'3.41'
__version__ = u'3.43'
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
import sys, os, time, datetime, random, socket, csv, platform, re, calendar, base64, hashlib
try:
import json
except ImportError:
import simplejson as json
import sys, os, time, datetime, random, socket, csv, platform, re, calendar, base64, hashlib, string
import json
import httplib2
import apiclient
import apiclient.discovery
import apiclient.errors
import apiclient.http
import googleapiclient
import googleapiclient.discovery
import googleapiclient.errors
import googleapiclient.http
import oauth2client.client
import oauth2client.file
import oauth2client.tools
import uritemplate
global true_values, false_values, prettyPrint, customerId, domain
global true_values, false_values, prettyPrint, customerId, domain, usergroup_types
true_values = [u'on', u'yes', u'enabled', u'true', u'1']
false_values = [u'off', u'no', u'disabled', u'false', u'0']
usergroup_types = [u'user', u'users', u'group', u'ou', u'org',
u'ou_and_children', u'ou_and_child', u'query',
u'license', u'licenses', u'file', u'all',
u'cros']
def convertUTF8(data):
import collections
if isinstance(data, str):
@@ -128,7 +128,7 @@ Usage: gam [OPTIONS]...
Dito GAM. Retrieve or set Google Apps domain,
user, group and alias settings. Exhaustive list of commands
can be found at: http://code.google.com/p/google-apps-manager/wiki
can be found at: https://github.com/jay0lee/GAM/wiki
Examples:
gam info domain
@@ -152,7 +152,7 @@ def getGamPath():
def doGAMVersion():
import struct
print u'Dito GAM %s - http://git.io/gam\n%s\nPython %s.%s.%s %s-bit %s\ngoogle-api-python-client %s\n%s %s\nPath: %s' % (__version__, __author__,
sys.version_info[0], sys.version_info[1], sys.version_info[2], struct.calcsize('P')*8, sys.version_info[3], apiclient.__version__,
sys.version_info[0], sys.version_info[1], sys.version_info[2], struct.calcsize('P')*8, sys.version_info[3], googleapiclient.__version__,
platform.platform(), platform.machine(), getGamPath())
def doGAMCheckForUpdates():
@@ -188,7 +188,7 @@ def doGAMCheckForUpdates():
visit_gam = raw_input(u"\n\nHit Y to visit the GAM website and download the latest release. Hit Enter to just continue with this boring old version. GAM won't bother you with this announcemnt for 1 week or you can create a file named noupdatecheck.txt in the same location as gam.py or gam.exe and GAM won't ever check for updates: ")
if visit_gam.lower() == u'y':
import webbrowser
webbrowser.open(u'http://google-apps-manager.googlecode.com')
webbrowser.open(u'https://github.com/jay0lee/GAM/releases')
print u'GAM is now exiting so that you can overwrite this old version with the latest release'
sys.exit(0)
f = open(getGamPath()+u'lastupdatecheck.txt', 'w')
@@ -372,7 +372,7 @@ def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_re
for n in range(1, retries+1):
try:
return method(prettyPrint=prettyPrint, **kwargs).execute()
except apiclient.errors.HttpError, e:
except googleapiclient.errors.HttpError, e:
try:
error = json.loads(e.content)
except ValueError:
@@ -398,17 +398,6 @@ def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_re
time.sleep(wait_on_fail)
if n > 3: sys.stderr.write(u'attempt %s/%s\n' % (n+1, retries))
continue
'''if reason == 'insufficientPermissions':
oauth2file = getGamPath()+'oauth2.txt'
try:
oauth2file = getGamPath()+os.environ['OAUTHFILE']
except KeyError:
pass
doRequestOAuth(incremental_auth=True)
storage = oauth2client.file.Storage(oauth2file)
credentials = storage.get()
service._http.request.credentials.access_token = credentials.access_token
continue'''
sys.stderr.write(u'Error %s: %s - %s\n\n' % (http_status, message, reason))
if soft_errors:
if n != 1:
@@ -546,14 +535,14 @@ def buildGAPIObject(api):
if api in [u'directory', u'reports']:
api = u'admin'
try:
service = apiclient.discovery.build(api, version, http=http)
except apiclient.errors.UnknownApiNameOrVersion:
service = googleapiclient.discovery.build(api, version, http=http)
except googleapiclient.errors.UnknownApiNameOrVersion:
disc_file = getGamPath()+u'%s-%s.json' % (api, version)
if os.path.isfile(disc_file):
f = file(disc_file, 'rb')
discovery = f.read()
f.close()
service = apiclient.discovery.build_from_document(discovery, base=u'https://www.googleapis.com', http=http)
service = googleapiclient.discovery.build_from_document(discovery, base=u'https://www.googleapis.com', http=http)
else:
raise
except httplib2.CertificateValidationUnsupported:
@@ -572,7 +561,7 @@ def buildGAPIObject(api):
customerId = u'my_customer'
return service
def buildGAPIServiceObject(api, act_as=None):
def buildGAPIServiceObject(api, act_as=None, soft_errors=False):
global prettyPrint
oauth2servicefile = getGamPath()+u'oauth2service'
try:
@@ -583,18 +572,18 @@ def buildGAPIServiceObject(api, act_as=None):
oauth2servicefilep12 = u'%s.p12' % oauth2servicefile
try:
json_string = open(oauth2servicefilejson).read()
json_data = json.loads(json_string)
try:
SERVICE_ACCOUNT_EMAIL = json_data[u'web'][u'client_email']
SERVICE_ACCOUNT_CLIENT_ID = json_data[u'web'][u'client_id']
f = file(oauth2servicefilep12, 'rb')
key = f.read()
f.close()
except IOError, e:
print u'Error: %s' % e
print u''
print u'Please follow the instructions at:\n\nhttps://code.google.com/p/google-apps-manager/wiki/GAM3OAuthServiceAccountSetup\n\nto setup a Service Account'
sys.exit(6)
except IOError, e:
print u'Error: %s' % e
print u''
print u'Please follow the instructions at:\n\nhttps://github.com/jay0lee/GAM/wiki/CreatingClientSecretsFile#creating-your-own-oauth2servicejson\n\nto setup a Service Account'
sys.exit(6)
json_data = json.loads(json_string)
try:
SERVICE_ACCOUNT_EMAIL = json_data[u'web'][u'client_email']
SERVICE_ACCOUNT_CLIENT_ID = json_data[u'web'][u'client_id']
f = file(oauth2servicefilep12, 'rb')
key = f.read()
f.close()
except KeyError:
# new format with config and data in the .json file...
SERVICE_ACCOUNT_EMAIL = json_data[u'client_email']
@@ -623,13 +612,15 @@ def buildGAPIServiceObject(api, act_as=None):
http = credentials.authorize(http)
version = getAPIVer(api)
try:
return apiclient.discovery.build(api, version, http=http)
return googleapiclient.discovery.build(api, version, http=http)
except oauth2client.client.AccessTokenRefreshError, e:
if e.message == u'access_denied':
if e.message in [u'access_denied', u'unauthorized_client: Unauthorized client or scope in request.']:
print u'Error: Access Denied. Please make sure the Client Name:\n\n%s\n\nis authorized for the API Scope(s):\n\n%s\n\nThis can be configured in your Control Panel under:\n\nSecurity -->\nAdvanced Settings -->\nManage third party OAuth Client access' % (SERVICE_ACCOUNT_CLIENT_ID, ','.join(scope))
sys.exit(5)
else:
print u'Error: %s' % e
if soft_errors:
return False
sys.exit(4)
def buildDiscoveryObject(api):
@@ -647,10 +638,10 @@ def buildDiscoveryObject(api):
if not os.path.isfile(getGamPath()+u'nocache.txt'):
cache = u'%sgamcache' % getGamPath()
http = httplib2.Http(ca_certs=getGamPath()+u'cacert.pem', disable_ssl_certificate_validation=disable_ssl_certificate_validation, cache=cache)
requested_url = uritemplate.expand(apiclient.discovery.DISCOVERY_URI, params)
requested_url = uritemplate.expand(googleapiclient.discovery.DISCOVERY_URI, params)
resp, content = http.request(requested_url)
if resp.status == 404:
raise apiclient.errors.UnknownApiNameOrVersion("name: %s version: %s" % (api, version))
raise googleapiclient.errors.UnknownApiNameOrVersion("name: %s version: %s" % (api, version))
if resp.status >= 400:
raise HttpError(resp, content, uri=requested_url)
try:
@@ -770,7 +761,7 @@ def showReport():
page_message = u'Got %%num_items%% users\n'
usage = callGAPIpages(service=rep.userUsageReport(), function=u'get', items=u'usageReports', page_message=page_message, throw_reasons=[u'invalid'], date=str(try_date), userKey=userKey, customerId=customerId, filters=filters, parameters=parameters)
break
except apiclient.errors.HttpError, e:
except googleapiclient.errors.HttpError, e:
error = json.loads(e.content)
try:
message = error[u'error'][u'errors'][0][u'message']
@@ -807,7 +798,7 @@ def showReport():
try:
usage = callGAPIpages(service=rep.customerUsageReports(), function=u'get', items=u'usageReports', throw_reasons=[u'invalid'], customerId=customerId, date=str(try_date), parameters=parameters)
break
except apiclient.errors.HttpError, e:
except googleapiclient.errors.HttpError, e:
error = json.loads(e.content)
try:
message = error[u'error'][u'errors'][0][u'message']
@@ -843,11 +834,13 @@ def showReport():
for app in auth_apps: # put apps at bottom
cust_attributes.append(app)
output_csv(csv_list=cust_attributes, titles=titles, list_type=u'Customer Report - %s' % try_date, todrive=to_drive)
elif report in [u'doc', u'docs', u'login', u'logins', u'admin', u'drive']:
elif report in [u'doc', u'docs', u'login', u'logins', u'admin', u'drive', u'token', u'tokens']:
if report == u'doc':
report = u'docs'
elif report == u'logins':
report = u'login'
elif report == u'tokens':
report = u'token'
page_message = u'Got %%num_items%% items\n'
activities = callGAPIpages(service=rep.activities(), function=u'list', page_message=page_message, applicationName=report, userKey=userKey, customerId=customerId, actorIpAddress=actorIpAddress, startTime=startTime, endTime=endTime, eventName=eventName, filters=filters)
if len(activities) > 0:
@@ -1315,7 +1308,7 @@ def doCalendarAddACL(calendarId=None, act_as=None, role=None, scope=None, entity
if body[u'scope'][u'type'] == u'domain':
try:
body[u'scope'][u'value'] = sys.argv[6].lower()
except KeyError:
except IndexError:
body[u'scope'][u'value'] = domain
callGAPI(service=cal.acl(), function=u'insert', calendarId=calendarId, body=body)
@@ -1331,7 +1324,7 @@ def doCalendarUpdateACL():
def doCalendarDelACL():
calendarId = sys.argv[2]
entity = sys.argv[4].lower()
entity = sys.argv[5].lower()
scope = u'user'
if entity == u'domain':
scope = u'domain'
@@ -1515,7 +1508,7 @@ def doPhoto(users):
import urllib2
try:
f = urllib2.urlopen(filename)
image_data = f.read()
image_data = str(f.read())
except urllib2.HTTPError, e:
print e
continue
@@ -1545,11 +1538,12 @@ def getPhoto(users):
i += 1
try:
photo = callGAPI(service=cd.users().photos(), function=u'get', throw_reasons=[u'notFound'], userKey=user)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
print u' no photo for %s' % user
continue
try:
photo_data = photo[u'photoData']
photo_data = str(photo[u'photoData'])
print photo_data
photo_data = base64.urlsafe_b64decode(photo_data)
except KeyError:
print u' no photo for %s' % user
@@ -1676,9 +1670,11 @@ def doDriveActivity(users):
for user in users:
activity = buildGAPIServiceObject(u'appsactivity', user)
page_message = u'Retrieved %%%%total_items%%%% activities for %s' % user
feed = callGAPIpages(service=activity.activities(), function=u'list', items=u'activities', page_message=page_message, source=u'drive.google.com', userId=u'me', drive_ancestorId=drive_ancestorId, pageSize=100)
feed = callGAPIpages(service=activity.activities(), function=u'list', items=u'activities',
page_message=page_message, source=u'drive.google.com', userId=u'me',
drive_ancestorId=drive_ancestorId, groupingStrategy=u'none', pageSize=500)
for item in feed:
activity_attributes.append(flatten_json(item))
activity_attributes.append(flatten_json(item)[u'combinedEvent'])
for an_item in activity_attributes[-1].keys():
if an_item not in activity_attributes[0]:
activity_attributes[0][an_item] = an_item
@@ -1705,7 +1701,11 @@ def delDriveFileACL(users):
permissionId = unicode(sys.argv[6])
for user in users:
drive = buildGAPIServiceObject(u'drive', user)
if not permissionId.isnumeric() and permissionId.lower() not in [u'anyone',]:
if permissionId[:3].lower() == u'id:':
permissionId = permissionId[3:]
elif permissionId.lower() in [u'anyone']:
pass
else:
permissionId = callGAPI(service=drive.permissions(), function=u'getIdForEmail', email=permissionId, fields=u'id')[u'id']
print u'Removing permission for %s from %s' % (permissionId, fileId)
callGAPI(service=drive.permissions(), function=u'delete', fileId=fileId, permissionId=permissionId)
@@ -1756,7 +1756,7 @@ def updateDriveFileACL(users):
fileId = sys.argv[5]
permissionId = unicode(sys.argv[6])
transferOwnership = None
body = {u'type': perm_type}
body = {}
i = 7
while i < len(sys.argv):
if sys.argv[i].lower().replace(u'_', u'') == u'withlink':
@@ -1784,7 +1784,9 @@ def updateDriveFileACL(users):
sys.exit(9)
for user in users:
drive = buildGAPIServiceObject(u'drive', user)
if not permissionId.isnumeric():
if permissionId[:3].lower() == u'id:':
permissionId = permissionId[3:]
else:
permissionId = callGAPI(service=drive.permissions(), function=u'getIdForEmail', email=permissionId, fields=u'id')[u'id']
print u'updating permissions for %s to file %s' % (permissionId, fileId)
result = callGAPI(service=drive.permissions(), function=u'patch', fileId=fileId, permissionId=permissionId, transferOwnership=transferOwnership, body=body)
@@ -2130,7 +2132,7 @@ def doUpdateDriveFile(users):
if drivefilename:
fileIds = doDriveSearch(drive, query=u'"me" in owners and title = "%s"' % drivefilename)
if local_filepath:
media_body = apiclient.http.MediaFileUpload(local_filepath, mimetype=mimetype, resumable=True)
media_body = googleapiclient.http.MediaFileUpload(local_filepath, mimetype=mimetype, resumable=True)
for fileId in fileIds:
if operation == u'update':
if media_body:
@@ -2245,7 +2247,7 @@ def createDriveFile(users):
for a_parent in more_parents:
body[u'parents'].append({u'id': a_parent})
if local_filepath:
media_body = apiclient.http.MediaFileUpload(local_filepath, mimetype=mimetype, resumable=True)
media_body = googleapiclient.http.MediaFileUpload(local_filepath, mimetype=mimetype, resumable=True)
result = callGAPI(service=drive.files(), function=u'insert', convert=convert, ocr=ocr, ocrLanguage=ocrLanguage, media_body=media_body, body=body, fields='id')
try:
print u'Successfully uploaded %s to Drive file ID %s' % (local_filename, result[u'id'])
@@ -2888,14 +2890,14 @@ def doDeleteLabel(users):
continue
del_me_count = len(del_labels)
i = 1
dbatch = apiclient.http.BatchHttpRequest()
dbatch = googleapiclient.http.BatchHttpRequest()
for del_me in del_labels:
print u' deleting label %s (%s/%s)' % (del_me[u'name'], i, del_me_count)
i += 1
dbatch.add(gmail.users().labels().delete(userId=user, id=del_me[u'id']), callback=label_del_result)
if len(dbatch._order) == 25:
dbatch.execute()
dbatch = apiclient.http.BatchHttpRequest()
dbatch = googleapiclient.http.BatchHttpRequest()
if len(dbatch._order) > 0:
dbatch.execute()
@@ -2926,6 +2928,29 @@ def showLabels(users):
print u' %s: %s' % (a_key, label[a_key])
print u''
def showGmailProfile(users):
todrive = False
i = 6
while i < len(sys.argv):
if sys.argv[i].lower() == u'todrive':
todrive = True
i += 1
else:
print u'Error %s is not a valid argument for gam ... show gmailprofiles.'
sys.exit(1)
profiles = [{}]
for user in users:
print 'Getting Gmail profile for %s' % user
gmail = buildGAPIServiceObject(u'gmail', act_as=user, soft_errors=True)
if not gmail:
continue
results = callGAPI(service=gmail.users(), function=u'getProfile', userId=u'me', soft_errors=True)
for item in results.keys():
if item not in profiles[0]:
profiles[0][item] = item
profiles.append(results)
output_csv(csv_list=profiles, titles=profiles[0], list_type=u'Gmail Profiles', todrive=todrive)
def updateLabels(users):
label_name = sys.argv[5]
body = {}
@@ -2986,46 +3011,37 @@ def renameLabels(users):
for user in users:
gmail = buildGAPIServiceObject(u'gmail', act_as=user)
labels = callGAPI(service=gmail.users().labels(), function=u'list', userId=user)
already_renamed_parents = list()
for label in labels[u'labels']:
if label[u'type'] == u'system':
continue
already_renamed_child = False
for already_renamed_parent in already_renamed_parents:
parent_length = len(already_renamed_parent)
if label[u'name'][:parent_length+1] == '%s/' % already_renamed_parent:
already_renamed_child = True
break
if already_renamed_child:
continue
match_result = re.search(pattern, label[u'name'])
if match_result != None:
new_label_name = replace % match_result.groups()
print u' Renaming "%s" to "%s"' % (label[u'name'], new_label_name)
try:
callGAPI(service=gmail.users().labels(), function=u'patch', soft_errors=True, throw_reasons=[u'aborted'], id=label[u'id'], userId=user, body={u'name': new_label_name})
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
if merge:
print u' Merging %s label to existing %s label' % (label[u'name'], new_label_name)
q = u'label:"%s"' % label[u'name']
print q
messages_to_relabel = callGAPIpages(service=gmail.users().messages(), function=u'list', items=u'messages', userId=user, q=q)
for new_label in labels[u'labels']:
if new_label[u'name'].lower() == new_label_name.lower():
new_label_id = new_label[u'id']
body = {u'addLabelIds': [new_label_id]}
break
i = 1
for message_to_relabel in messages_to_relabel:
print u' relabeling message %s (%s/%s)' % (message_to_relabel[u'id'], i, len(messages_to_relabel))
callGAPI(service=gmail.users().messages(), function=u'modify', userId=user, id=message_to_relabel[u'id'], body=body)
i += 1
if len(messages_to_relabel) > 0:
for new_label in labels[u'labels']:
if new_label[u'name'].lower() == new_label_name.lower():
new_label_id = new_label[u'id']
body = {u'addLabelIds': [new_label_id]}
break
i = 1
for message_to_relabel in messages_to_relabel:
print u' relabeling message %s (%s/%s)' % (message_to_relabel[u'id'], i, len(messages_to_relabel))
callGAPI(service=gmail.users().messages(), function=u'modify', userId=user, id=message_to_relabel[u'id'], body=body)
i += 1
else:
print u' no messages with %s label' % label[u'name']
print u' Deleting label %s' % label[u'name']
callGAPI(service=gmail.users().labels(), function=u'delete', id=label[u'id'], userId=user)
else:
print u' Error: looks like %s already exists, not renaming. Use the "merge" argument to merge the labels' % new_label_name
continue
already_renamed_parents.append(label)
def doFilter(users):
i = 4 # filter arguments start here
@@ -3837,7 +3853,7 @@ def doCreateAlias():
elif target_type == u'target':
try:
callGAPI(service=cd.users().aliases(), function=u'insert', throw_reasons=[u'invalid'], userKey=targetKey, body=body)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
callGAPI(service=cd.groups().aliases(), function=u'insert', groupKey=targetKey, body=body)
def doCreateOrg():
@@ -4299,7 +4315,7 @@ def doUpdateGroup():
if role not in [u'OWNER', u'MANAGER', u'MEMBER']:
role = u'MEMBER'
i = 5
if sys.argv[i].lower() in [u'user', u'users', u'group', u'ou', u'org', u'query', u'file', u'all']:
if sys.argv[i].lower() in usergroup_types:
users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1])
else:
users_email = [sys.argv[i],]
@@ -4321,7 +4337,7 @@ def doUpdateGroup():
print u'added %s to group' % result[u'email']
except TypeError:
pass
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
pass
elif sys.argv[4].lower() == u'sync':
role = sys.argv[5].upper()
@@ -4339,7 +4355,7 @@ def doUpdateGroup():
sys.stderr.write(u' adding %s %s\n' % (role, user_email))
try:
result = callGAPI(service=cd.members(), function=u'insert', soft_errors=True, throw_reasons=[u'duplicate'], groupKey=group, body={u'email': user_email, u'role': role})
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
result = callGAPI(service=cd.members(), function=u'update', soft_errors=True, groupKey=group, memberKey=user_email, body={u'email': user_email, u'role': role})
for user_email in to_remove:
sys.stderr.write(u' removing %s\n' % user_email)
@@ -4348,7 +4364,7 @@ def doUpdateGroup():
i = 5
if sys.argv[i].lower() in [u'member', u'manager', u'owner']:
i += 1
if sys.argv[i].lower() in [u'user', u'users', u'group', u'ou', u'org', u'query', u'file', u'all']:
if sys.argv[i].lower() in usergroup_types:
user_emails = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1])
else:
user_emails = [sys.argv[i],]
@@ -4446,7 +4462,7 @@ def doUpdateAlias():
target_email = u'%s@%s' % (target_email, domain)
try:
callGAPI(service=cd.users().aliases(), function=u'delete', throw_reasons=[u'invalid'], userKey=alias, alias=alias)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
callGAPI(service=cd.groups().aliases(), function=u'delete', groupKey=alias, alias=alias)
if target_type == u'user':
callGAPI(service=cd.users().aliases(), function=u'insert', userKey=target_email, body={u'alias': alias})
@@ -4455,7 +4471,7 @@ def doUpdateAlias():
elif target_type == u'target':
try:
callGAPI(service=cd.users().aliases(), function=u'insert', throw_reasons=[u'invalid'], userKey=target_email, body={u'alias': alias})
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
callGAPI(service=cd.groups().aliases(), function=u'insert', groupKey=target_email, body={u'alias': alias})
print u'updated alias %s' % alias
@@ -4534,8 +4550,10 @@ def doUpdateMobile():
action_body[u'action'] = sys.argv[i+1].lower()
if action_body[u'action'] == u'wipe':
action_body[u'action'] = u'admin_remote_wipe'
if action_body[u'action'] not in [u'admin_remote_wipe', u'approve', u'block', u'cancel_remote_wipe_then_activate', u'cancel_remote_wipe_then_block']:
print u'Error: action must be wipe, approve, block, cancel_remote_wipe_then_activate or cancel_remote_wipe_then_block. Got %s' % action_body[u'action']
elif action_body[u'action'].replace(u'_', '') in [u'accountwipe', u'wipeaccount']:
action_body[u'action'] = u'admin_account_wipe'
if action_body[u'action'] not in [u'admin_remote_wipe', u'admin_account_wipe', u'approve', u'block', u'cancel_remote_wipe_then_activate', u'cancel_remote_wipe_then_block']:
print u'Error: action must be wipe, wipeaccount, approve, block, cancel_remote_wipe_then_activate or cancel_remote_wipe_then_block. Got %s' % action_body[u'action']
sys.exit(5)
doAction = True
i += 2
@@ -4568,7 +4586,7 @@ def doUpdateOrg():
orgUnitPath = sys.argv[3]
cd = buildGAPIObject(u'directory')
if sys.argv[4].lower() in [u'move', u'add']:
if sys.argv[5].lower() in [u'user', u'users', u'cros', u'group', u'ou', u'org', u'query', u'file', u'all']:
if sys.argv[5].lower() in usergroup_types:
users = getUsersToModify(entity_type=sys.argv[5], entity=sys.argv[6])
else:
users = getUsersToModify(entity_type=u'user', entity=sys.argv[5])
@@ -4588,7 +4606,7 @@ def doUpdateOrg():
sys.stderr.write(u' moving %s to %s (%s/%s)\n' % (user, orgUnitPath, current_user, user_count))
try:
callGAPI(service=cd.users(), function=u'patch', throw_reasons=[u'conditionNotMet'], userKey=user, body={u'orgUnitPath': orgUnitPath})
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
pass
current_user += 1
else:
@@ -4631,12 +4649,12 @@ def doWhatIs():
sys.stderr.write(u'%s is a user alias\n\n' % email)
doGetAliasInfo(alias_email=email)
return
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
sys.stderr.write(u'%s is not a user...\n' % email)
sys.stderr.write(u'%s is not a user alias...\n' % email)
try:
group = callGAPI(service=cd.groups(), function=u'get', throw_reasons=[u'notFound', u'badRequest'], groupKey=email, fields=u'email')
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
sys.stderr.write(u'%s is not a group either!\n\nDoesn\'t seem to exist!\n\n' % email)
sys.exit(1)
if group[u'email'].lower() == email.lower():
@@ -4819,10 +4837,10 @@ def doGetUserInfo(user_email=None):
for alias in user[u'nonEditableAliases']:
print u' %s' % alias
if getGroups:
groups = callGAPI(service=cd.groups(), function=u'list', userKey=user_email)
if u'groups' in groups:
print u'Groups:'
for group in groups[u'groups']:
groups = callGAPIpages(service=cd.groups(), function=u'list', items=u'groups', userKey=user_email, fields=u'groups(name,email),nextPageToken')
if len(groups) > 0:
print u'Groups: (%s)' % len(groups)
for group in groups:
print u' %s <%s>' % (group[u'name'], group[u'email'])
if getLicenses:
print u'Licenses:'
@@ -4835,7 +4853,7 @@ def doGetUserInfo(user_email=None):
productId, skuId = getProductAndSKU(sku)
try:
result = callGAPI(service=lic.licenseAssignments(), function=u'get', throw_reasons=['notFound'], userId=user_email, productId=productId, skuId=skuId)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
continue
print u' %s' % result[u'skuId']
@@ -4857,7 +4875,7 @@ def doGetGroupInfo(group_name=None):
basic_info = callGAPI(service=cd.groups(), function=u'get', groupKey=group_name)
try:
settings = callGAPI(service=gs.groups(), function=u'get', retry_reasons=[u'serviceLimit'], groupUniqueId=basic_info[u'email'], throw_reasons=u'authError') # Use email address retrieved from cd since GS API doesn't support uid
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
pass
print u''
print u'Group Settings:'
@@ -4903,7 +4921,7 @@ def doGetAliasInfo(alias_email=None):
alias_email = u'%s@%s' % (alias_email, domain)
try:
result = callGAPI(service=cd.users(), function=u'get', throw_reasons=[u'invalid', u'badRequest'], userKey=alias_email)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
result = callGAPI(service=cd.groups(), function=u'get', groupKey=alias_email)
print u' Alias Email: %s' % alias_email
try:
@@ -4935,21 +4953,34 @@ def doGetCrosInfo():
deviceId = sys.argv[3]
cd = buildGAPIObject(u'directory')
info = callGAPI(service=cd.chromeosdevices(), function=u'get', customerId=customerId, deviceId=deviceId)
for key, value in info.items():
if key in [u'kind', u'etag']:
continue
print u' %s: %s' % (key, value)
print_json(None, info)
def doGetMobileInfo():
deviceId = sys.argv[3]
cd = buildGAPIObject(u'directory')
info = callGAPI(service=cd.mobiledevices(), function=u'get', customerId=customerId, resourceId=deviceId)
for key, value in info.items():
if key == u'kind':
continue
if key in [u'name', u'email']:
value = value[0]
print u' %s: %s' % (key, value)
print_json(None, info)
def print_json(object_name, object_value, spacing=u''):
if object_name in [u'kind', u'etag', u'etags']:
return
if object_name != None:
sys.stdout.write(u'%s%s: ' % (spacing, object_name))
if type(object_value) is list:
if len(object_value) == 1:
sys.stdout.write(u'%s\n' % object_value[0])
return
sys.stdout.write(u'\n')
for a_value in object_value:
if type(a_value) in (str, unicode):
print u' %s%s' % (spacing, a_value)
else:
print_json(object_name=None, object_value=a_value, spacing=u' %s' % spacing)
elif type(object_value) is dict:
for another_object in object_value.keys():
print_json(object_name=another_object, object_value=object_value[another_object], spacing=spacing)
else:
sys.stdout.write(u'%s\n' % (object_value))
def doUpdateNotification():
cd = buildGAPIObject(u'directory')
@@ -5075,7 +5106,7 @@ def doSiteVerifyAttempt():
body = {u'site':{u'type':verify_type, u'identifier':identifier}, u'verificationMethod':verificationMethod}
try:
verify_result = callGAPI(service=verif.webResource(), function=u'insert', throw_reasons=[u'badRequest'], verificationMethod=verificationMethod, body=body)
except apiclient.errors.HttpError, e:
except googleapiclient.errors.HttpError, e:
error = json.loads(e.content)
message = error[u'error'][u'errors'][0][u'message']
print u'ERROR: %s' % message
@@ -5232,7 +5263,7 @@ def doGetBackupCodes(users):
for user in users:
try:
codes = callGAPI(service=cd.verificationCodes(), function=u'list', throw_reasons=[u'invalidArgument', u'invalid'], userKey=user)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
codes = dict()
codes[u'items'] = list()
print u'Backup verification codes for %s' % user
@@ -5271,7 +5302,7 @@ def doDelBackupCodes(users):
for user in users:
try:
codes = callGAPI(service=cd.verificationCodes(), function=u'invalidate', soft_errors=True, throw_reasons=[u'invalid',], userKey=user)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
print u'No 2SV backup codes for %s' % user
continue
print u'2SV backup codes for %s invalidated' % user
@@ -5297,7 +5328,7 @@ def doGetTokens(users):
for user in users:
try:
token = callGAPI(service=cd.tokens(), function=u'get', throw_reasons=[u'notFound',], userKey=user, clientId=clientId, fields=u'clientId')
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
continue
print u'%s has allowed this token' % user
return
@@ -5314,7 +5345,7 @@ def doGetTokens(users):
print u' %s:' % item
for it in token[item]:
print u' %s' % it
if type(token[item]) is unicode:
if type(token[item]) in (unicode, bool):
try:
print u' %s: %s' % (item, token[item])
except UnicodeEncodeError:
@@ -5348,7 +5379,7 @@ def doDeprovUser(users):
print u'Invalidating 2SV Backup Codes for %s' % user
try:
codes = callGAPI(service=cd.verificationCodes(), function=u'invalidate', soft_errors=True, throw_reasons=[u'invalid'], userKey=user)
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
print u'No 2SV Backup Codes'
print u'Getting tokens for %s...' % user
tokens = callGAPI(service=cd.tokens(), function=u'list', userKey=user, fields=u'items/clientId')
@@ -5682,7 +5713,7 @@ def doDeleteAlias(alias_email=None):
try:
callGAPI(service=cd.users().aliases(), function=u'delete', throw_reasons=[u'invalid', u'badRequest', u'notFound'], userKey=alias_email, alias=alias_email)
return
except apiclient.errors.HttpError, e:
except googleapiclient.errors.HttpError, e:
error = json.loads(e.content)
reason = error[u'error'][u'errors'][0][u'reason']
if reason == u'notFound':
@@ -5724,7 +5755,7 @@ def output_csv(csv_list, titles, list_type, todrive):
convert = False
drive = buildGAPIObject(u'drive')
string_data = string_file.getvalue()
media = apiclient.http.MediaInMemoryUpload(string_data, mimetype=u'text/csv')
media = googleapiclient.http.MediaInMemoryUpload(string_data, mimetype=u'text/csv')
result = callGAPI(service=drive.files(), function=u'insert', convert=convert, body={u'description': u' '.join(sys.argv), u'title': u'%s - %s' % (domain, list_type), u'mimeType': u'text/csv'}, media_body=media)
file_url = result[u'alternateLink']
if os.path.isfile(getGamPath()+u'nobrowser.txt'):
@@ -5817,6 +5848,9 @@ def doPrintUsers():
user_fields.append(u'suspended')
user_fields.append(u'suspensionReason')
i += 1
elif sys.argv[i].lower() == u'ismailboxsetup':
user_fields.append(u'isMailboxSetup')
i += 1
elif sys.argv[i].lower() == u'changepassword':
user_fields.append(u'changePasswordAtNextLogin')
i += 1
@@ -6360,10 +6394,9 @@ def doPrintCrosDevices():
cros_attributes.append(crosdevice)
output_csv(cros_attributes, titles, 'CrOS', todrive)
def doPrintLicenses(return_list=False):
def doPrintLicenses(return_list=False, skus=None):
lic = buildGAPIObject(u'licensing')
products = [u'Google-Apps', u'Google-Drive-storage', u'Google-Coordinate', u'Google-Vault']
skus = None
licenses = []
lic_attributes = [{}]
todrive = False
@@ -6387,14 +6420,14 @@ def doPrintLicenses(return_list=False):
page_message = u'Got %%%%total_items%%%% Licenses for %s...\n' % sku
try:
licenses += callGAPIpages(service=lic.licenseAssignments(), function=u'listForProductAndSku', throw_reasons=[u'invalid', u'forbidden'], page_message=page_message, customerId=domain, productId=product, skuId=sku, fields=u'items(productId,skuId,userId),nextPageToken')
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
licenses += []
else:
for productId in products:
page_message = u'Got %%%%total_items%%%% Licenses for %s...\n' % productId
try:
licenses += callGAPIpages(service=lic.licenseAssignments(), function=u'listForProduct', throw_reasons=[u'invalid', u'forbidden'], page_message=page_message, customerId=domain, productId=productId, fields=u'items(productId,skuId,userId),nextPageToken')
except apiclient.errors.HttpError:
except googleapiclient.errors.HttpError:
licenses = +[]
for license in licenses:
a_license = dict()
@@ -6418,7 +6451,7 @@ def doPrintTokens():
if sys.argv[i].lower() == u'todrive':
todrive = True
i += 1
elif sys.argv[i].lower() in [u'user', u'users', u'group', u'ou', u'org', u'query', u'file', u'all']:
elif sys.argv[i].lower() in usergroup_types:
entity_type = sys.argv[i].lower()
entity = sys.argv[i+1].lower()
i += 2
@@ -6936,6 +6969,14 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
else:
users.append(member[u'primaryEmail'])
if not silent: sys.stderr.write(u"done.\r\n")
elif entity_type in [u'license', u'licenses']:
users = []
licenses = doPrintLicenses(return_list=True, skus=entity.split(u','))
for row in licenses[1:]: # skip header
try:
users.append(row[u'userId'])
except KeyError:
pass
elif entity_type == u'file':
users = []
filename = entity
@@ -7075,12 +7116,6 @@ class cmd_flags(object):
self.auth_host_port = [8080, 9090]
def doRequestOAuth(incremental_auth=False):
if not os.path.isfile(getGamPath()+u'nodito.txt'):
print u"\n\nGAM is made possible and maintained by the work of Dito. Who is Dito?\n\nDito is solely focused on moving organizations to Google's cloud. After hundreds of successful deployments over the last 5 years, we have gained notoriety for our complete understanding of the platform, our change management & training ability, and our rock-star deployment engineers. We are known worldwide as the Google Apps Experts.\n"
visit_dito = raw_input(u"Want to learn more about Dito? Hit Y to visit our website (you can switch back to this window when you're done). Hit Enter to continue without visiting Dito: ")
if visit_dito.lower() == u'y':
import webbrowser
webbrowser.open(u'http://www.ditoweb.com?s=gam')
CLIENT_SECRETS = getGamPath()+u'client_secrets.json'
MISSING_CLIENT_SECRETS_MESSAGE = u"""
WARNING: Please configure OAuth 2.0
@@ -7092,7 +7127,10 @@ found at:
with information from the APIs Console <https://cloud.google.com/console>.
See https://code.google.com/p/google-apps-manager/wiki/CreatingClientSecretsFile
See:
https://github.com/jay0lee/GAM/wiki/CreatingClientSecretsFile
for instructions.
""" % CLIENT_SECRETS
@@ -7285,7 +7323,7 @@ try:
argv = shlex.split(line)
if argv[0] in [u'#', u' ', u''] or len(argv) < 2:
continue
elif argv.pop(0).lower() != u'gam':
elif argv.pop(0).lower() not in [u'gam', u'commit-batch']:
print u'Error: "%s" is not a valid gam command' % line
continue
items.append(python_cmd+argv)
@@ -7571,6 +7609,8 @@ try:
showDriveFileInfo(users)
elif readWhat == u'sendas':
showSendAs(users)
elif readWhat == u'gmailprofile':
showGmailProfile(users)
elif readWhat in [u'sig', u'signature']:
getSignature(users)
elif readWhat == u'forward':

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2012 Google Inc.
# Copyright 2014 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.
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "1.2"
__version__ = "1.3.1"

View File

@@ -59,7 +59,7 @@ Example of unsubscribing.
import datetime
import uuid
from apiclient import errors
from googleapiclient import errors
from oauth2client import util

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -27,9 +27,12 @@ __all__ = [
# Standard library imports
import StringIO
import copy
from email.generator import Generator
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
import json
import keyword
import logging
import mimetypes
@@ -49,20 +52,20 @@ import mimeparse
import uritemplate
# Local imports
from apiclient.errors import HttpError
from apiclient.errors import InvalidJsonError
from apiclient.errors import MediaUploadSizeError
from apiclient.errors import UnacceptableMimeTypeError
from apiclient.errors import UnknownApiNameOrVersion
from apiclient.errors import UnknownFileType
from apiclient.http import HttpRequest
from apiclient.http import MediaFileUpload
from apiclient.http import MediaUpload
from apiclient.model import JsonModel
from apiclient.model import MediaModel
from apiclient.model import RawModel
from apiclient.schema import Schemas
from oauth2client.anyjson import simplejson
from googleapiclient.errors import HttpError
from googleapiclient.errors import InvalidJsonError
from googleapiclient.errors import MediaUploadSizeError
from googleapiclient.errors import UnacceptableMimeTypeError
from googleapiclient.errors import UnknownApiNameOrVersion
from googleapiclient.errors import UnknownFileType
from googleapiclient.http import HttpRequest
from googleapiclient.http import MediaFileUpload
from googleapiclient.http import MediaUpload
from googleapiclient.model import JsonModel
from googleapiclient.model import MediaModel
from googleapiclient.model import RawModel
from googleapiclient.schema import Schemas
from oauth2client.client import GoogleCredentials
from oauth2client.util import _add_query_parameter
from oauth2client.util import positional
@@ -74,12 +77,8 @@ logger = logging.getLogger(__name__)
URITEMPLATE = re.compile('{[^}]*}')
VARNAME = re.compile('[a-zA-Z0-9_-]+')
if httplib2.debuglevel > 0:
prettyPrint = 'true'
else:
prettyPrint = 'false'
DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/'
'{api}/{apiVersion}/rest?prettyPrint=%s' % prettyPrint)
'{api}/{apiVersion}/rest')
DEFAULT_METHOD_DOC = 'A description of how to use this function'
HTTP_PAYLOAD_METHODS = frozenset(['PUT', 'POST', 'PATCH'])
_MEDIA_SIZE_BIT_SHIFTS = {'KB': 10, 'MB': 20, 'GB': 30, 'TB': 40}
@@ -150,7 +149,8 @@ def build(serviceName,
discoveryServiceUrl=DISCOVERY_URI,
developerKey=None,
model=None,
requestBuilder=HttpRequest):
requestBuilder=HttpRequest,
credentials=None):
"""Construct a Resource for interacting with an API.
Construct a Resource object for interacting with an API. The serviceName and
@@ -167,9 +167,11 @@ def build(serviceName,
document for that service.
developerKey: string, key obtained from
https://code.google.com/apis/console.
model: apiclient.Model, converts to and from the wire format.
requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP
model: googleapiclient.Model, converts to and from the wire format.
requestBuilder: googleapiclient.http.HttpRequest, encapsulator for an HTTP
request.
credentials: oauth2client.Credentials, credentials to be used for
authentication.
Returns:
A Resource object with methods for interacting with the service.
@@ -191,7 +193,7 @@ def build(serviceName,
if 'REMOTE_ADDR' in os.environ:
requested_url = _add_query_parameter(requested_url, 'userIp',
os.environ['REMOTE_ADDR'])
logger.info('URL being requested: %s' % requested_url)
logger.info('URL being requested: GET %s' % requested_url)
resp, content = http.request(requested_url)
@@ -202,13 +204,14 @@ def build(serviceName,
raise HttpError(resp, content, uri=requested_url)
try:
service = simplejson.loads(content)
service = json.loads(content)
except ValueError, e:
logger.error('Failed to parse as JSON: ' + content)
raise InvalidJsonError()
return build_from_document(content, base=discoveryServiceUrl, http=http,
developerKey=developerKey, model=model, requestBuilder=requestBuilder)
developerKey=developerKey, model=model, requestBuilder=requestBuilder,
credentials=credentials)
@positional(1)
@@ -219,7 +222,8 @@ def build_from_document(
http=None,
developerKey=None,
model=None,
requestBuilder=HttpRequest):
requestBuilder=HttpRequest,
credentials=None):
"""Create a Resource for interacting with an API.
Same as `build()`, but constructs the Resource object from a discovery
@@ -240,6 +244,7 @@ def build_from_document(
model: Model class instance that serializes and de-serializes requests and
responses.
requestBuilder: Takes an http request and packages it up to be executed.
credentials: object, credentials to be used for authentication.
Returns:
A Resource object with methods for interacting with the service.
@@ -249,10 +254,32 @@ def build_from_document(
future = {}
if isinstance(service, basestring):
service = simplejson.loads(service)
service = json.loads(service)
base = urlparse.urljoin(service['rootUrl'], service['servicePath'])
schema = Schemas(service)
if credentials:
# If credentials were passed in, we could have two cases:
# 1. the scopes were specified, in which case the given credentials
# are used for authorizing the http;
# 2. the scopes were not provided (meaning the Application Default
# Credentials are to be used). In this case, the Application Default
# Credentials are built and used instead of the original credentials.
# If there are no scopes found (meaning the given service requires no
# authentication), there is no authorization of the http.
if (isinstance(credentials, GoogleCredentials) and
credentials.create_scoped_required()):
scopes = service.get('auth', {}).get('oauth2', {}).get('scopes', {})
if scopes:
credentials = credentials.create_scoped(scopes.keys())
else:
# No need to authorize the http object
# if the service does not require authentication.
credentials = None
if credentials:
http = credentials.authorize(http)
if model is None:
features = service.get('features', [])
model = JsonModel('dataWrapper' in features)
@@ -703,14 +730,19 @@ def createMethod(methodName, methodDesc, rootDesc, schema):
payload = media_upload.getbytes(0, media_upload.size())
msg.set_payload(payload)
msgRoot.attach(msg)
body = msgRoot.as_string()
# encode the body: note that we can't use `as_string`, because
# it plays games with `From ` lines.
fp = StringIO.StringIO()
g = Generator(fp, mangle_from_=False)
g.flatten(msgRoot, unixfrom=False)
body = fp.getvalue()
multipart_boundary = msgRoot.get_boundary()
headers['content-type'] = ('multipart/related; '
'boundary="%s"') % multipart_boundary
url = _add_query_parameter(url, 'uploadType', 'multipart')
logger.info('URL being requested: %s' % url)
logger.info('URL being requested: %s %s' % (httpMethod,url))
return self._requestBuilder(self._http,
model.response,
url,
@@ -818,7 +850,7 @@ Returns:
request.uri = uri
logger.info('URL being requested: %s' % uri)
logger.info('URL being requested: %s %s' % (methodName,uri))
return request
@@ -836,9 +868,9 @@ class Resource(object):
http: httplib2.Http, Object to make http requests with.
baseUrl: string, base URL for the API. All requests are relative to this
URI.
model: apiclient.Model, converts to and from the wire format.
model: googleapiclient.Model, converts to and from the wire format.
requestBuilder: class or callable that instantiates an
apiclient.HttpRequest object.
googleapiclient.HttpRequest object.
developerKey: string, key obtained from
https://code.google.com/apis/console
resourceDesc: object, section of deserialized discovery document that

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python2.4
#
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -22,9 +22,9 @@ should be defined in this file.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json
from oauth2client import util
from oauth2client.anyjson import simplejson
class Error(Exception):
@@ -45,7 +45,7 @@ class HttpError(Error):
"""Calculate the reason for the error from the response content."""
reason = self.resp.reason
try:
data = simplejson.loads(self.content)
data = json.loads(self.content)
reason = data['error']['message']
except (ValueError, KeyError):
pass

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2012 Google Inc.
# Copyright 2014 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.
@@ -26,6 +26,7 @@ import base64
import copy
import gzip
import httplib2
import json
import logging
import mimeparse
import mimetypes
@@ -49,7 +50,6 @@ from errors import UnexpectedBodyError
from errors import UnexpectedMethodError
from model import JsonModel
from oauth2client import util
from oauth2client.anyjson import simplejson
DEFAULT_CHUNK_SIZE = 512*1024
@@ -221,7 +221,7 @@ class MediaUpload(object):
del d[member]
d['_class'] = t.__name__
d['_module'] = t.__module__
return simplejson.dumps(d)
return json.dumps(d)
def to_json(self):
"""Create a JSON representation of an instance of MediaUpload.
@@ -244,7 +244,7 @@ class MediaUpload(object):
An instance of the subclass of MediaUpload that was serialized with
to_json().
"""
data = simplejson.loads(s)
data = json.loads(s)
# Find and call the right classmethod from_json() to restore the object.
module = data['_module']
m = __import__(module, fromlist=module.split('.')[:-1])
@@ -436,7 +436,7 @@ class MediaFileUpload(MediaIoBaseUpload):
@staticmethod
def from_json(s):
d = simplejson.loads(s)
d = json.loads(s)
return MediaFileUpload(d['_filename'], mimetype=d['_mimetype'],
chunksize=d['_chunksize'], resumable=d['_resumable'])
@@ -497,7 +497,7 @@ class MediaIoBaseDownload(object):
Args:
fd: io.Base or file object, The stream in which to write the downloaded
bytes.
request: apiclient.http.HttpRequest, the media request to perform in
request: googleapiclient.http.HttpRequest, the media request to perform in
chunks.
chunksize: int, File will be downloaded in chunks of this many bytes.
"""
@@ -529,7 +529,7 @@ class MediaIoBaseDownload(object):
downloaded.
Raises:
apiclient.errors.HttpError if the response was not a 2xx.
googleapiclient.errors.HttpError if the response was not a 2xx.
httplib2.HttpLib2Error if a transport error has occured.
"""
headers = {
@@ -676,7 +676,7 @@ class HttpRequest(object):
by the postproc.
Raises:
apiclient.errors.HttpError if the response was not a 2xx.
googleapiclient.errors.HttpError if the response was not a 2xx.
httplib2.HttpLib2Error if a transport error has occured.
"""
if http is None:
@@ -771,7 +771,7 @@ class HttpRequest(object):
The body will be None until the resumable media is fully uploaded.
Raises:
apiclient.errors.HttpError if the response was not a 2xx.
googleapiclient.errors.HttpError if the response was not a 2xx.
httplib2.HttpLib2Error if a transport error has occured.
"""
if http is None:
@@ -885,7 +885,7 @@ class HttpRequest(object):
The body will be None until the resumable media is fully uploaded.
Raises:
apiclient.errors.HttpError if the response was not a 2xx or a 308.
googleapiclient.errors.HttpError if the response was not a 2xx or a 308.
"""
if resp.status in [200, 201]:
self._in_error_state = False
@@ -913,12 +913,12 @@ class HttpRequest(object):
del d['_sleep']
del d['_rand']
return simplejson.dumps(d)
return json.dumps(d)
@staticmethod
def from_json(s, http, postproc):
"""Returns an HttpRequest populated with info from a JSON object."""
d = simplejson.loads(s)
d = json.loads(s)
if d['resumable'] is not None:
d['resumable'] = MediaUpload.new_from_json(d['resumable'])
return HttpRequest(
@@ -936,7 +936,7 @@ class BatchHttpRequest(object):
"""Batches multiple HttpRequest objects into a single HTTP request.
Example:
from apiclient.http import BatchHttpRequest
from googleapiclient.http import BatchHttpRequest
def list_animals(request_id, response, exception):
\"\"\"Do something with the animals list response.\"\"\"
@@ -973,7 +973,7 @@ class BatchHttpRequest(object):
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
third is an googleapiclient.errors.HttpError exception object if an HTTP error
occurred while processing the request, or None if no error occurred.
batch_uri: string, URI to send batch requests to.
"""
@@ -1178,7 +1178,7 @@ class BatchHttpRequest(object):
callback: callable, A callback to be called for this 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
third is an googleapiclient.errors.HttpError exception object if an HTTP error
occurred while processing the request, or None if no errors occurred.
request_id: string, A unique id for the request. The id will be passed to
the callback with the response.
@@ -1211,7 +1211,7 @@ class BatchHttpRequest(object):
Raises:
httplib2.HttpLib2Error if a transport error has occured.
apiclient.errors.BatchError if the response is the wrong format.
googleapiclient.errors.BatchError if the response is the wrong format.
"""
message = MIMEMultipart('mixed')
# Message should not write out it's own headers.
@@ -1229,7 +1229,12 @@ class BatchHttpRequest(object):
msg.set_payload(body)
message.attach(msg)
body = message.as_string()
# encode the body: note that we can't use `as_string`, because
# it plays games with `From ` lines.
fp = StringIO.StringIO()
g = Generator(fp, mangle_from_=False)
g.flatten(message, unixfrom=False)
body = fp.getvalue()
headers = {}
headers['content-type'] = ('multipart/mixed; '
@@ -1275,7 +1280,7 @@ class BatchHttpRequest(object):
Raises:
httplib2.HttpLib2Error if a transport error has occured.
apiclient.errors.BatchError if the response is the wrong format.
googleapiclient.errors.BatchError if the response is the wrong format.
"""
# If http is not supplied use the first valid one given in the requests.
@@ -1381,7 +1386,7 @@ class RequestMockBuilder(object):
'plus.activities.get': (None, response),
}
)
apiclient.discovery.build("plus", "v1", requestBuilder=requestBuilder)
googleapiclient.discovery.build("plus", "v1", requestBuilder=requestBuilder)
Methods that you do not supply a response for will return a
200 OK with an empty string as the response content or raise an excpetion
@@ -1425,8 +1430,8 @@ class RequestMockBuilder(object):
# or expecting a body and not provided one.
raise UnexpectedBodyError(expected_body, body)
if isinstance(expected_body, str):
expected_body = simplejson.loads(expected_body)
body = simplejson.loads(body)
expected_body = json.loads(expected_body)
body = json.loads(body)
if body != expected_body:
raise UnexpectedBodyError(expected_body, body)
return HttpRequestMock(resp, content, postproc)
@@ -1517,7 +1522,7 @@ class HttpMockSequence(object):
if content == 'echo_request_headers':
content = headers
elif content == 'echo_request_headers_as_json':
content = simplejson.dumps(headers)
content = json.dumps(headers)
elif content == 'echo_request_body':
if hasattr(body, 'read'):
content = body.read()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2007 Joe Gregorio
# Copyright 2014 Joe Gregorio
#
# Licensed under the MIT License

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python2.4
#
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -24,12 +24,12 @@ object representation.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json
import logging
import urllib
from apiclient import __version__
from googleapiclient import __version__
from errors import HttpError
from oauth2client.anyjson import simplejson
dump_request_response = False
@@ -77,7 +77,7 @@ class Model(object):
The body de-serialized as a Python object.
Raises:
apiclient.errors.HttpError if a non 2xx response is received.
googleapiclient.errors.HttpError if a non 2xx response is received.
"""
_abstract()
@@ -125,7 +125,7 @@ class BaseModel(Model):
path_params: dict, parameters that appear in the request path
query_params: dict, parameters that appear in the query
body_value: object, the request body as a Python object, which must be
serializable by simplejson.
serializable by json.
Returns:
A tuple of (headers, path_params, query, body)
@@ -193,7 +193,7 @@ class BaseModel(Model):
The body de-serialized as a Python object.
Raises:
apiclient.errors.HttpError if a non 2xx response is received.
googleapiclient.errors.HttpError if a non 2xx response is received.
"""
self._log_response(resp, content)
# Error handling is TBD, for example, do we retry
@@ -254,11 +254,11 @@ class JsonModel(BaseModel):
if (isinstance(body_value, dict) and 'data' not in body_value and
self._data_wrapper):
body_value = {'data': body_value}
return simplejson.dumps(body_value)
return json.dumps(body_value)
def deserialize(self, content):
content = content.decode('utf-8')
body = simplejson.loads(content)
body = json.loads(content)
if self._data_wrapper and isinstance(body, dict) and 'data' in body:
body = body['data']
return body

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2013 Google Inc.
# Copyright 2014 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.
@@ -25,13 +25,13 @@ import argparse
import httplib2
import os
from apiclient import discovery
from googleapiclient import discovery
from oauth2client import client
from oauth2client import file
from oauth2client import tools
def init(argv, name, version, doc, filename, scope=None, parents=[]):
def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_filename=None):
"""A common initialization routine for samples.
Many of the sample applications do the same initialization, which has now
@@ -49,6 +49,7 @@ def init(argv, name, version, doc, filename, scope=None, parents=[]):
file: string, filename of the application. Usually set to __file__.
parents: list of argparse.ArgumentParser, additional command-line flags.
scope: string, The OAuth scope used.
discovery_filename: string, name of local discovery file (JSON). Use when discovery doc not available via URL.
Returns:
A tuple of (service, flags), where service is the service object and flags
@@ -88,6 +89,14 @@ def init(argv, name, version, doc, filename, scope=None, parents=[]):
credentials = tools.run_flow(flow, storage, flags)
http = credentials.authorize(http = httplib2.Http())
# Construct a service object via the discovery service.
service = discovery.build(name, version, http=http)
if discovery_filename is None:
# Construct a service object via the discovery service.
service = discovery.build(name, version, http=http)
else:
# Construct a service object using a local discovery document file.
with open(discovery_filename) as discovery_file:
service = discovery.build_from_document(
discovery_file.read(),
base='https://www.googleapis.com/',
http=http)
return (service, flags)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -64,7 +64,6 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
import copy
from oauth2client import util
from oauth2client.anyjson import simplejson
class Schemas(object):

View File

@@ -1,5 +1,8 @@
__version__ = "1.2"
"""Client library for using OAuth2, especially with Google APIs."""
__version__ = '1.3.1'
GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'
GOOGLE_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token'

View File

@@ -1,32 +0,0 @@
# Copyright (C) 2010 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.
"""Utility module to import a JSON module
Hides all the messy details of exactly where
we get a simplejson module from.
"""
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
try: # pragma: no cover
# Should work for Python2.6 and higher.
import json as simplejson
except ImportError: # pragma: no cover
try:
import simplejson
except ImportError:
# Try to import from django, should work on App Engine
from django.utils import simplejson

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -19,14 +19,14 @@ Utilities for making it easier to use OAuth 2.0 on Google App Engine.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import base64
import cgi
import httplib2
import json
import logging
import os
import pickle
import threading
import time
import httplib2
from google.appengine.api import app_identity
from google.appengine.api import memcache
@@ -41,7 +41,6 @@ from oauth2client import GOOGLE_TOKEN_URI
from oauth2client import clientsecrets
from oauth2client import util
from oauth2client import xsrfutil
from oauth2client.anyjson import simplejson
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import AssertionCredentials
from oauth2client.client import Credentials
@@ -159,15 +158,20 @@ class AppAssertionCredentials(AssertionCredentials):
Args:
scope: string or iterable of strings, scope(s) of the credentials being
requested.
**kwargs: optional keyword args, including:
service_account_id: service account id of the application. If None or
unspecified, the default service account for the app is used.
"""
self.scope = util.scopes_to_string(scope)
self._kwargs = kwargs
self.service_account_id = kwargs.get('service_account_id', None)
# Assertion type is no longer used, but still in the parent class signature.
super(AppAssertionCredentials, self).__init__(None)
@classmethod
def from_json(cls, json):
data = simplejson.loads(json)
def from_json(cls, json_data):
data = json.loads(json_data)
return AppAssertionCredentials(data['scope'])
def _refresh(self, http_request):
@@ -186,11 +190,22 @@ class AppAssertionCredentials(AssertionCredentials):
"""
try:
scopes = self.scope.split()
(token, _) = app_identity.get_access_token(scopes)
except app_identity.Error, e:
(token, _) = app_identity.get_access_token(
scopes, service_account_id=self.service_account_id)
except app_identity.Error as e:
raise AccessTokenRefreshError(str(e))
self.access_token = token
@property
def serialization_data(self):
raise NotImplementedError('Cannot serialize credentials for AppEngine.')
def create_scoped_required(self):
return not self.scope
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self._kwargs)
class FlowProperty(db.Property):
"""App Engine datastore Property for Flow.
@@ -434,6 +449,7 @@ class StorageByKeyName(Storage):
entity_key = db.Key.from_path(self._model.kind(), self._key_name)
db.delete(entity_key)
@db.non_transactional(allow_existing=True)
def locked_get(self):
"""Retrieve Credential from datastore.
@@ -456,6 +472,7 @@ class StorageByKeyName(Storage):
credentials.set_store(self)
return credentials
@db.non_transactional(allow_existing=True)
def locked_put(self, credentials):
"""Write a Credentials to the datastore.
@@ -468,6 +485,7 @@ class StorageByKeyName(Storage):
if self._cache:
self._cache.set(self._key_name, credentials.to_json())
@db.non_transactional(allow_existing=True)
def locked_delete(self):
"""Delete Credential from datastore."""
@@ -650,8 +668,9 @@ class OAuth2Decorator(object):
provided to this constructor. A string indicating the name of the field
on the _credentials_class where a Credentials object will be stored.
Defaults to 'credentials'.
**kwargs: dict, Keyword arguments are be passed along as kwargs to the
OAuth2WebServerFlow constructor.
**kwargs: dict, Keyword arguments are passed along as kwargs to
the OAuth2WebServerFlow constructor.
"""
self._tls = threading.local()
self.flow = None
@@ -798,14 +817,18 @@ class OAuth2Decorator(object):
url = self.flow.step1_get_authorize_url()
return str(url)
def http(self):
def http(self, *args, **kwargs):
"""Returns an authorized http instance.
Must only be called from within an @oauth_required decorated method, or
from within an @oauth_aware decorated method where has_credentials()
returns True.
Args:
*args: Positional arguments passed to httplib2.Http constructor.
**kwargs: Positional arguments passed to httplib2.Http constructor.
"""
return self.credentials.authorize(httplib2.Http())
return self.credentials.authorize(httplib2.Http(*args, **kwargs))
@property
def callback_path(self):
@@ -858,7 +881,7 @@ class OAuth2Decorator(object):
user)
if decorator._token_response_param and credentials.token_response:
resp_json = simplejson.dumps(credentials.token_response)
resp_json = json.dumps(credentials.token_response)
redirect_uri = util._add_query_parameter(
redirect_uri, decorator._token_response_param, resp_json)
@@ -904,7 +927,7 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
"""
@util.positional(3)
def __init__(self, filename, scope, message=None, cache=None):
def __init__(self, filename, scope, message=None, cache=None, **kwargs):
"""Constructor
Args:
@@ -917,17 +940,20 @@ class OAuth2DecoratorFromClientSecrets(OAuth2Decorator):
decorator.
cache: An optional cache service client that implements get() and set()
methods. See clientsecrets.loadfile() for details.
**kwargs: dict, Keyword arguments are passed along as kwargs to
the OAuth2WebServerFlow constructor.
"""
client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
if client_type not in [
clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED]:
raise InvalidClientSecretsError(
'OAuth2Decorator doesn\'t support this OAuth 2.0 flow.')
constructor_kwargs = {
'auth_uri': client_info['auth_uri'],
'token_uri': client_info['token_uri'],
'message': message,
}
"OAuth2Decorator doesn't support this OAuth 2.0 flow.")
constructor_kwargs = dict(kwargs)
constructor_kwargs.update({
'auth_uri': client_info['auth_uri'],
'token_uri': client_info['token_uri'],
'message': message,
})
revoke_uri = client_info.get('revoke_uri')
if revoke_uri is not None:
constructor_kwargs['revoke_uri'] = revoke_uri

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2011 Google Inc.
# Copyright 2014 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.
@@ -20,8 +20,8 @@ an OAuth 2.0 protected service.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import json
from anyjson import simplejson
# Properties that make a client_secrets.json file valid.
TYPE_WEB = 'web'
@@ -87,12 +87,12 @@ def _validate_clientsecrets(obj):
def load(fp):
obj = simplejson.load(fp)
obj = json.load(fp)
return _validate_clientsecrets(obj)
def loads(s):
obj = simplejson.loads(s)
obj = json.loads(s)
return _validate_clientsecrets(obj)
@@ -100,7 +100,7 @@ def _loadfile(filename):
try:
fp = file(filename, 'r')
try:
obj = simplejson.load(fp)
obj = json.load(fp)
finally:
fp.close()
except IOError:

View File

@@ -1,7 +1,6 @@
#!/usr/bin/python2.4
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Google Inc.
# Copyright 2014 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.
@@ -14,14 +13,13 @@
# 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.
"""Crypto-related routines for oauth2client."""
import base64
import hashlib
import json
import logging
import time
from anyjson import simplejson
CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
AUTH_TOKEN_LIFETIME_SECS = 300 # 5 minutes in seconds
@@ -38,7 +36,6 @@ class AppIdentityError(Exception):
try:
from OpenSSL import crypto
class OpenSSLVerifier(object):
"""Verifies the signature on a message."""
@@ -125,10 +122,11 @@ try:
Raises:
OpenSSL.crypto.Error if the key can't be parsed.
"""
if key.startswith('-----BEGIN '):
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key)
parsed_pem_key = _parse_pem_key(key)
if parsed_pem_key:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
else:
pkey = crypto.load_pkcs12(key, password).get_privatekey()
pkey = crypto.load_pkcs12(key, password.encode('utf8')).get_privatekey()
return OpenSSLSigner(pkey)
except ImportError:
@@ -140,6 +138,7 @@ try:
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
from Crypto.Signature import PKCS1_v1_5
from Crypto.Util.asn1 import DerSequence
class PyCryptoVerifier(object):
@@ -181,14 +180,15 @@ try:
Returns:
Verifier instance.
Raises:
NotImplementedError if is_x509_cert is true.
"""
if is_x509_cert:
raise NotImplementedError(
'X509 certs are not supported by the PyCrypto library. '
'Try using PyOpenSSL if native code is an option.')
pemLines = key_pem.replace(' ', '').split()
certDer = _urlsafe_b64decode(''.join(pemLines[1:-1]))
certSeq = DerSequence()
certSeq.decode(certDer)
tbsSeq = DerSequence()
tbsSeq.decode(certSeq[0])
pubkey = RSA.importKey(tbsSeq[6])
else:
pubkey = RSA.importKey(key_pem)
return PyCryptoVerifier(pubkey)
@@ -230,11 +230,12 @@ try:
Raises:
NotImplementedError if they key isn't in PEM format.
"""
if key.startswith('-----BEGIN '):
pkey = RSA.importKey(key)
parsed_pem_key = _parse_pem_key(key)
if parsed_pem_key:
pkey = RSA.importKey(parsed_pem_key)
else:
raise NotImplementedError(
'PKCS12 format is not supported by the PyCrpto library. '
'PKCS12 format is not supported by the PyCrypto library. '
'Try converting to a "PEM" '
'(openssl pkcs12 -in xxxxx.p12 -nodes -nocerts > privatekey.pem) '
'or using PyOpenSSL if native code is an option.')
@@ -256,6 +257,23 @@ else:
'PyOpenSSL, or PyCrypto 2.6 or later')
def _parse_pem_key(raw_key_input):
"""Identify and extract PEM keys.
Determines whether the given key is in the format of PEM key, and extracts
the relevant part of the key if it is.
Args:
raw_key_input: The contents of a private key file (either PEM or PKCS12).
Returns:
string, The actual key if the contents are from a PEM file, or else None.
"""
offset = raw_key_input.find('-----BEGIN ')
if offset != -1:
return raw_key_input[offset:]
def _urlsafe_b64encode(raw_bytes):
return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
@@ -268,7 +286,7 @@ def _urlsafe_b64decode(b64string):
def _json_encode(data):
return simplejson.dumps(data, separators = (',', ':'))
return json.dumps(data, separators=(',', ':'))
def make_signed_jwt(signer, payload):
@@ -286,8 +304,8 @@ def make_signed_jwt(signer, payload):
header = {'typ': 'JWT', 'alg': 'RS256'}
segments = [
_urlsafe_b64encode(_json_encode(header)),
_urlsafe_b64encode(_json_encode(payload)),
_urlsafe_b64encode(_json_encode(header)),
_urlsafe_b64encode(_json_encode(payload)),
]
signing_input = '.'.join(segments)
@@ -318,9 +336,8 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
"""
segments = jwt.split('.')
if (len(segments) != 3):
raise AppIdentityError(
'Wrong number of segments in token: %s' % jwt)
if len(segments) != 3:
raise AppIdentityError('Wrong number of segments in token: %s' % jwt)
signed = '%s.%s' % (segments[0], segments[1])
signature = _urlsafe_b64decode(segments[2])
@@ -328,15 +345,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
# Parse token.
json_body = _urlsafe_b64decode(segments[1])
try:
parsed = simplejson.loads(json_body)
parsed = json.loads(json_body)
except:
raise AppIdentityError('Can\'t parse token: %s' % json_body)
# Check signature.
verified = False
for (keyname, pem) in certs.items():
for _, pem in certs.items():
verifier = Verifier.from_string(pem, True)
if (verifier.verify(signed, signature)):
if verifier.verify(signed, signature):
verified = True
break
if not verified:
@@ -354,16 +371,15 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
if exp is None:
raise AppIdentityError('No exp field in token: %s' % json_body)
if exp >= now + MAX_TOKEN_LIFETIME_SECS:
raise AppIdentityError(
'exp field too far in future: %s' % json_body)
raise AppIdentityError('exp field too far in future: %s' % json_body)
latest = exp + CLOCK_SKEW_SECS
if now < earliest:
raise AppIdentityError('Token used too early, %d < %d: %s' %
(now, earliest, json_body))
(now, earliest, json_body))
if now > latest:
raise AppIdentityError('Token used too late, %d > %d: %s' %
(now, latest, json_body))
(now, latest, json_body))
# Check audience.
if audience is not None:
@@ -372,6 +388,6 @@ def verify_signed_jwt_with_certs(jwt, certs, audience):
raise AppIdentityError('No aud field in token: %s' % json_body)
if aud != audience:
raise AppIdentityError('Wrong recipient, %s != %s: %s' %
(aud, audience, json_body))
(aud, audience, json_body))
return parsed

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -116,14 +116,21 @@ class Storage(BaseStorage):
credential.set_store(self)
return credential
def locked_put(self, credentials):
def locked_put(self, credentials, overwrite=False):
"""Write a Credentials to the datastore.
Args:
credentials: Credentials, the credentials to store.
overwrite: Boolean, indicates whether you would like these credentials to
overwrite any existing stored credentials.
"""
args = {self.key_name: self.key_value}
entity = self.model_class(**args)
if overwrite:
entity, unused_is_new = self.model_class.objects.get_or_create(**args)
else:
entity = self.model_class(**args)
setattr(entity, self.property_name, credentials)
entity.save()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2010 Google Inc.
# Copyright 2014 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.
@@ -21,12 +21,10 @@ credentials.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import os
import stat
import threading
from anyjson import simplejson
from client import Storage as BaseStorage
from client import Credentials
from oauth2client.client import Credentials
from oauth2client.client import Storage as BaseStorage
class CredentialsFileSymbolicLinkError(Exception):

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2012 Google Inc.
# Copyright 2014 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.
@@ -19,12 +19,11 @@ Utilities for making it easier to use OAuth 2.0 on Google Compute Engine.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import httplib2
import json
import logging
import uritemplate
import urllib
from oauth2client import util
from oauth2client.anyjson import simplejson
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import AssertionCredentials
@@ -57,13 +56,14 @@ class AppAssertionCredentials(AssertionCredentials):
requested.
"""
self.scope = util.scopes_to_string(scope)
self.kwargs = kwargs
# Assertion type is no longer used, but still in the parent class signature.
super(AppAssertionCredentials, self).__init__(None)
@classmethod
def from_json(cls, json):
data = simplejson.loads(json)
def from_json(cls, json_data):
data = json.loads(json_data)
return AppAssertionCredentials(data['scope'])
def _refresh(self, http_request):
@@ -78,13 +78,28 @@ class AppAssertionCredentials(AssertionCredentials):
Raises:
AccessTokenRefreshError: When the refresh fails.
"""
uri = uritemplate.expand(META, {'scope': self.scope})
query = '?scope=%s' % urllib.quote(self.scope, '')
uri = META.replace('{?scope}', query)
response, content = http_request(uri)
if response.status == 200:
try:
d = simplejson.loads(content)
except StandardError, e:
d = json.loads(content)
except StandardError as e:
raise AccessTokenRefreshError(str(e))
self.access_token = d['accessToken']
else:
if response.status == 404:
content += (' This can occur if a VM was created'
' with no service account or scopes.')
raise AccessTokenRefreshError(content)
@property
def serialization_data(self):
raise NotImplementedError(
'Cannot serialize credentials for GCE service accounts.')
def create_scoped_required(self):
return not self.scope
def create_scoped(self, scopes):
return AppAssertionCredentials(scopes, **self.kwargs)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2012 Google Inc.
# Copyright 2014 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.
@@ -19,11 +19,12 @@ A Storage for Credentials that uses the keyring module.
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
import keyring
import threading
from client import Storage as BaseStorage
from client import Credentials
import keyring
from oauth2client.client import Credentials
from oauth2client.client import Storage as BaseStorage
class Storage(BaseStorage):

View File

@@ -1,4 +1,4 @@
# Copyright 2011 Google Inc.
# Copyright 2014 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.
@@ -70,6 +70,7 @@ class _Opener(object):
self._mode = mode
self._fallback_mode = fallback_mode
self._fh = None
self._lock_fd = None
def is_locked(self):
"""Was the file locked."""
@@ -122,7 +123,7 @@ class _PosixOpener(_Opener):
validate_file(self._filename)
try:
self._fh = open(self._filename, self._mode)
except IOError, e:
except IOError as e:
# If we can't access with _mode, try _fallback_mode and don't lock.
if e.errno == errno.EACCES:
self._fh = open(self._filename, self._fallback_mode)
@@ -137,12 +138,12 @@ class _PosixOpener(_Opener):
self._locked = True
break
except OSError, e:
except OSError as e:
if e.errno != errno.EEXIST:
raise
if (time.time() - start_time) >= timeout:
logger.warn('Could not acquire lock %s in %s seconds' % (
lock_filename, timeout))
logger.warn('Could not acquire lock %s in %s seconds',
lock_filename, timeout)
# Close the file and open in fallback_mode.
if self._fh:
self._fh.close()
@@ -192,9 +193,9 @@ try:
validate_file(self._filename)
try:
self._fh = open(self._filename, self._mode)
except IOError, e:
except IOError as e:
# If we can't access with _mode, try _fallback_mode and don't lock.
if e.errno == errno.EACCES:
if e.errno in (errno.EPERM, errno.EACCES):
self._fh = open(self._filename, self._fallback_mode)
return
@@ -204,7 +205,7 @@ try:
fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
self._locked = True
return
except IOError, e:
except IOError as e:
# If not retrying, then just pass on the error.
if timeout == 0:
raise e
@@ -212,8 +213,8 @@ try:
raise e
# We could not acquire the lock. Try again.
if (time.time() - start_time) >= timeout:
logger.warn('Could not lock %s in %s seconds' % (
self._filename, timeout))
logger.warn('Could not lock %s in %s seconds',
self._filename, timeout)
if self._fh:
self._fh.close()
self._fh = open(self._filename, self._fallback_mode)
@@ -267,7 +268,7 @@ try:
validate_file(self._filename)
try:
self._fh = open(self._filename, self._mode)
except IOError, e:
except IOError as e:
# If we can't access with _mode, try _fallback_mode and don't lock.
if e.errno == errno.EACCES:
self._fh = open(self._filename, self._fallback_mode)
@@ -284,7 +285,7 @@ try:
pywintypes.OVERLAPPED())
self._locked = True
return
except pywintypes.error, e:
except pywintypes.error as e:
if timeout == 0:
raise e
@@ -308,7 +309,7 @@ try:
try:
hfile = win32file._get_osfhandle(self._fh.fileno())
win32file.UnlockFileEx(hfile, 0, -0x10000, pywintypes.OVERLAPPED())
except pywintypes.error, e:
except pywintypes.error as e:
if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
raise
self._locked = False

View File

@@ -1,4 +1,4 @@
# Copyright 2011 Google Inc.
# Copyright 2014 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.
@@ -43,17 +43,15 @@ The format of the stored data is like so:
__author__ = 'jbeda@google.com (Joe Beda)'
import base64
import errno
import json
import logging
import os
import threading
from anyjson import simplejson
from oauth2client.client import Storage as BaseStorage
from oauth2client.client import Credentials
from oauth2client.client import Storage as BaseStorage
from oauth2client import util
from locked_file import LockedFile
from oauth2client.locked_file import LockedFile
logger = logging.getLogger(__name__)
@@ -286,7 +284,7 @@ class _MultiStore(object):
if self._warn_on_readonly:
logger.warn('The credentials file (%s) is not writable. Opening in '
'read-only mode. Any refreshed credentials will only be '
'valid for this run.' % self._file.filename())
'valid for this run.', self._file.filename())
if os.path.getsize(self._file.filename()) == 0:
logger.debug('Initializing empty multistore file')
# The multistore is empty so write out an empty file.
@@ -315,7 +313,7 @@ class _MultiStore(object):
"""
assert self._thread_lock.locked()
self._file.file_handle().seek(0)
return simplejson.load(self._file.file_handle())
return json.load(self._file.file_handle())
def _locked_json_write(self, data):
"""Write a JSON serializable data structure to the multistore.
@@ -329,7 +327,7 @@ class _MultiStore(object):
if self._read_only:
return
self._file.file_handle().seek(0)
simplejson.dump(data, self._file.file_handle(), sort_keys=True, indent=2)
json.dump(data, self._file.file_handle(), sort_keys=True, indent=2, separators=(',', ': '))
self._file.file_handle().truncate()
def _refresh_data_cache(self):
@@ -387,7 +385,7 @@ class _MultiStore(object):
raw_key = cred_entry['key']
key = util.dict_to_tuple_key(raw_key)
credential = None
credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
credential = Credentials.new_from_json(json.dumps(cred_entry['credential']))
return (key, credential)
def _write(self):
@@ -400,7 +398,7 @@ class _MultiStore(object):
raw_data['data'] = raw_creds
for (cred_key, cred) in self._data.items():
raw_key = dict(cred_key)
raw_cred = simplejson.loads(cred.to_json())
raw_cred = json.loads(cred.to_json())
raw_creds.append({'key': raw_key, 'credential': raw_cred})
self._locked_json_write(raw_data)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2013 Google Inc.
# Copyright 2014 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.
@@ -96,7 +96,7 @@ def run(flow, storage, http=None):
try:
httpd = ClientRedirectServer((FLAGS.auth_host_name, port),
ClientRedirectHandler)
except socket.error, e:
except socket.error as e:
pass
else:
success = True
@@ -150,7 +150,7 @@ def run(flow, storage, http=None):
try:
credential = flow.step2_exchange(code, http=http)
except client.FlowExchangeError, e:
except client.FlowExchangeError as e:
sys.exit('Authentication has failed: %s' % e)
storage.put(credential)

View File

@@ -0,0 +1,132 @@
# Copyright 2014 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.
"""A service account credentials class.
This credentials class is implemented on top of rsa library.
"""
import base64
import json
import time
from pyasn1.codec.ber import decoder
from pyasn1_modules.rfc5208 import PrivateKeyInfo
import rsa
from oauth2client import GOOGLE_REVOKE_URI
from oauth2client import GOOGLE_TOKEN_URI
from oauth2client import util
from oauth2client.client import AssertionCredentials
class _ServiceAccountCredentials(AssertionCredentials):
"""Class representing a service account (signed JWT) credential."""
MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
def __init__(self, service_account_id, service_account_email, private_key_id,
private_key_pkcs8_text, scopes, user_agent=None,
token_uri=GOOGLE_TOKEN_URI, revoke_uri=GOOGLE_REVOKE_URI,
**kwargs):
super(_ServiceAccountCredentials, self).__init__(
None, user_agent=user_agent, token_uri=token_uri, revoke_uri=revoke_uri)
self._service_account_id = service_account_id
self._service_account_email = service_account_email
self._private_key_id = private_key_id
self._private_key = _get_private_key(private_key_pkcs8_text)
self._private_key_pkcs8_text = private_key_pkcs8_text
self._scopes = util.scopes_to_string(scopes)
self._user_agent = user_agent
self._token_uri = token_uri
self._revoke_uri = revoke_uri
self._kwargs = kwargs
def _generate_assertion(self):
"""Generate the assertion that will be used in the request."""
header = {
'alg': 'RS256',
'typ': 'JWT',
'kid': self._private_key_id
}
now = long(time.time())
payload = {
'aud': self._token_uri,
'scope': self._scopes,
'iat': now,
'exp': now + _ServiceAccountCredentials.MAX_TOKEN_LIFETIME_SECS,
'iss': self._service_account_email
}
payload.update(self._kwargs)
assertion_input = '%s.%s' % (
_urlsafe_b64encode(header),
_urlsafe_b64encode(payload))
# Sign the assertion.
signature = base64.urlsafe_b64encode(rsa.pkcs1.sign(
assertion_input, self._private_key, 'SHA-256')).rstrip('=')
return '%s.%s' % (assertion_input, signature)
def sign_blob(self, blob):
return (self._private_key_id,
rsa.pkcs1.sign(blob, self._private_key, 'SHA-256'))
@property
def service_account_email(self):
return self._service_account_email
@property
def serialization_data(self):
return {
'type': 'service_account',
'client_id': self._service_account_id,
'client_email': self._service_account_email,
'private_key_id': self._private_key_id,
'private_key': self._private_key_pkcs8_text
}
def create_scoped_required(self):
return not self._scopes
def create_scoped(self, scopes):
return _ServiceAccountCredentials(self._service_account_id,
self._service_account_email,
self._private_key_id,
self._private_key_pkcs8_text,
scopes,
user_agent=self._user_agent,
token_uri=self._token_uri,
revoke_uri=self._revoke_uri,
**self._kwargs)
def _urlsafe_b64encode(data):
return base64.urlsafe_b64encode(
json.dumps(data, separators=(',', ':')).encode('UTF-8')).rstrip('=')
def _get_private_key(private_key_pkcs8_text):
"""Get an RSA private key object from a pkcs8 representation."""
der = rsa.pem.load_pem(private_key_pkcs8_text, 'PRIVATE KEY')
asn1_private_key, _ = decoder.decode(der, asn1Spec=PrivateKeyInfo())
return rsa.PrivateKey.load_pkcs1(
asn1_private_key.getComponentByName('privateKey').asOctets(),
format='DER')

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2013 Google Inc.
# Copyright 2014 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.
@@ -23,24 +23,17 @@ __author__ = 'jcgregorio@google.com (Joe Gregorio)'
__all__ = ['argparser', 'run_flow', 'run', 'message_if_missing']
import BaseHTTPServer
#import argparse
import httplib2
import BaseHTTPServer
import logging
import os
import socket
import sys
import urlparse
import webbrowser
from oauth2client import client
from oauth2client import file
from oauth2client import util
try:
from urlparse import parse_qsl
except ImportError:
from cgi import parse_qsl
_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
To make this sample run you will need to populate the client_secrets.json file
@@ -52,20 +45,20 @@ with information from the APIs Console <https://code.google.com/apis/console>.
"""
# run_parser is an ArgumentParser that contains command-line options expected
# argparser is an ArgumentParser that contains command-line options expected
# by tools.run(). Pass it in as part of the 'parents' argument to your own
# ArgumentParser.
#argparser = argparse.ArgumentParser(add_help=False)
#argparser.add_argument('--auth_host_name', default='localhost',
# help='Hostname when running a local web server.')
# help='Hostname when running a local web server.')
#argparser.add_argument('--noauth_local_webserver', action='store_true',
# default=False, help='Do not run a local web server.')
# default=False, help='Do not run a local web server.')
#argparser.add_argument('--auth_host_port', default=[8080, 8090], type=int,
# nargs='*', help='Port web server should listen on.')
# nargs='*', help='Port web server should listen on.')
#argparser.add_argument('--logging_level', default='ERROR',
# choices=['DEBUG', 'INFO', 'WARNING', 'ERROR',
# 'CRITICAL'],
# help='Set the logging level of detail.')
# choices=['DEBUG', 'INFO', 'WARNING', 'ERROR',
# 'CRITICAL'],
# help='Set the logging level of detail.')
class ClientRedirectServer(BaseHTTPServer.HTTPServer):
@@ -84,26 +77,25 @@ class ClientRedirectHandler(BaseHTTPServer.BaseHTTPRequestHandler):
into the servers query_params and then stops serving.
"""
def do_GET(s):
def do_GET(self):
"""Handle a GET request.
Parses the query parameters and prints a message
if the flow has completed. Note that we can't detect
if an error occurred.
"""
s.send_response(200)
s.send_header("Content-type", "text/html")
s.end_headers()
query = s.path.split('?', 1)[-1]
query = dict(parse_qsl(query))
s.server.query_params = query
s.wfile.write("<html><head><title>Authentication Status</title></head>")
s.wfile.write("<body><p>The authentication flow has completed.</p>")
s.wfile.write("</body></html>")
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
query = self.path.split('?', 1)[-1]
query = dict(urlparse.parse_qsl(query))
self.server.query_params = query
self.wfile.write("<html><head><title>Authentication Status</title></head>")
self.wfile.write("<body><p>The authentication flow has completed.</p>")
self.wfile.write("</body></html>")
def log_message(self, format, *args):
"""Do not log messages to stdout while running as command line program."""
pass
@util.positional(3)
@@ -141,7 +133,7 @@ def run_flow(flow, storage, flags, http=None):
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[tools.run_parser])
parents=[tools.argparser])
flags = parser.parse_args(argv)
Args:
@@ -163,7 +155,7 @@ def run_flow(flow, storage, flags, http=None):
try:
httpd = ClientRedirectServer((flags.auth_host_name, port),
ClientRedirectHandler)
except socket.error, e:
except socket.error as e:
pass
else:
success = True
@@ -186,11 +178,14 @@ def run_flow(flow, storage, flags, http=None):
authorize_url = flow.step1_get_authorize_url()
if flags.short_url:
from apiclient.discovery import build
service = build('urlshortener', 'v1', http=http)
url_result = service.url().insert(body={'longUrl': authorize_url}).execute()
authorize_url = url_result['id']
try:
from googleapiclient.discovery import build
service = build('urlshortener', 'v1', http=http)
url_result = service.url().insert(body={'longUrl': authorize_url},
key=u'AIzaSyBlmgbii8QfJSYmC9VTMOfqrAt5Vj5wtzE').execute()
authorize_url = url_result['id']
except:
pass
if not flags.noauth_local_webserver:
webbrowser.open(authorize_url, new=1, autoraise=True)
print 'Your browser has been opened to visit:'
@@ -199,7 +194,6 @@ def run_flow(flow, storage, flags, http=None):
print
print 'If your browser is on a different machine then exit and re-run this'
print 'after creating a file called nobrowser.txt in the same path as GAM.'
# print 'If your browser is on a different machine then exit and re-run this'
# print 'application with the command-line parameter '
# print
# print ' --noauth_local_webserver'
@@ -225,7 +219,7 @@ def run_flow(flow, storage, flags, http=None):
try:
credential = flow.step2_exchange(code, http=http)
except client.FlowExchangeError, e:
except client.FlowExchangeError as e:
sys.exit('Authentication has failed: %s' % e)
storage.put(credential)
@@ -241,8 +235,8 @@ def message_if_missing(filename):
return _CLIENT_SECRETS_MESSAGE % filename
try:
from old_run import run
from old_run import FLAGS
from oauth2client.old_run import run
from oauth2client.old_run import FLAGS
except ImportError:
def run(*args, **kwargs):
raise NotImplementedError(

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright 2010 Google Inc.
# Copyright 2014 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.
@@ -17,14 +17,16 @@
"""Common utility library."""
__author__ = ['rafek@google.com (Rafe Kaplan)',
'guido@google.com (Guido van Rossum)',
__author__ = [
'rafek@google.com (Rafe Kaplan)',
'guido@google.com (Guido van Rossum)',
]
__all__ = [
'positional',
'POSITIONAL_WARNING',
'POSITIONAL_EXCEPTION',
'POSITIONAL_IGNORE',
'positional',
'POSITIONAL_WARNING',
'POSITIONAL_EXCEPTION',
'POSITIONAL_IGNORE',
]
import inspect
@@ -33,11 +35,6 @@ import types
import urllib
import urlparse
try:
from urlparse import parse_qsl
except ImportError:
from cgi import parse_qsl
logger = logging.getLogger(__name__)
POSITIONAL_WARNING = 'WARNING'
@@ -190,7 +187,7 @@ def _add_query_parameter(url, name, value):
return url
else:
parsed = list(urlparse.urlparse(url))
q = dict(parse_qsl(parsed[4]))
q = dict(urlparse.parse_qsl(parsed[4]))
q[name] = value
parsed[4] = urllib.urlencode(q)
return urlparse.urlunparse(parsed)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/python2.5
#
# Copyright 2010 the Melange authors.
# Copyright 2014 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,14 +17,13 @@
"""Helper methods for creating & verifying XSRF tokens."""
__authors__ = [
'"Doug Coker" <dcoker@google.com>',
'"Joe Gregorio" <jcgregorio@google.com>',
'"Doug Coker" <dcoker@google.com>',
'"Joe Gregorio" <jcgregorio@google.com>',
]
import base64
import hmac
import os # for urandom
import time
from oauth2client import util

View File

@@ -1,547 +0,0 @@
r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
interchange format.
:mod:`simplejson` exposes an API familiar to users of the standard library
:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
version of the :mod:`json` library contained in Python 2.6, but maintains
compatibility with Python 2.4 and Python 2.5 and (currently) has
significant performance advantages, even without using the optional C
extension for speedups.
Encoding basic Python object hierarchies::
>>> import simplejson as json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> print(json.dumps("\"foo\bar"))
"\"foo\bar"
>>> print(json.dumps(u'\u1234'))
"\u1234"
>>> print(json.dumps('\\'))
"\\"
>>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True))
{"a": 0, "b": 0, "c": 0}
>>> from simplejson.compat import StringIO
>>> io = StringIO()
>>> json.dump(['streaming API'], io)
>>> io.getvalue()
'["streaming API"]'
Compact encoding::
>>> import simplejson as json
>>> obj = [1,2,3,{'4': 5, '6': 7}]
>>> json.dumps(obj, separators=(',',':'), sort_keys=True)
'[1,2,3,{"4":5,"6":7}]'
Pretty printing::
>>> import simplejson as json
>>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' '))
{
"4": 5,
"6": 7
}
Decoding JSON::
>>> import simplejson as json
>>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
True
>>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
True
>>> from simplejson.compat import StringIO
>>> io = StringIO('["streaming API"]')
>>> json.load(io)[0] == 'streaming API'
True
Specializing JSON object decoding::
>>> import simplejson as json
>>> def as_complex(dct):
... if '__complex__' in dct:
... return complex(dct['real'], dct['imag'])
... return dct
...
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
... object_hook=as_complex)
(1+2j)
>>> from decimal import Decimal
>>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
True
Specializing JSON object encoding::
>>> import simplejson as json
>>> def encode_complex(obj):
... if isinstance(obj, complex):
... return [obj.real, obj.imag]
... raise TypeError(repr(o) + " is not JSON serializable")
...
>>> json.dumps(2 + 1j, default=encode_complex)
'[2.0, 1.0]'
>>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
'[2.0, 1.0]'
>>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
'[2.0, 1.0]'
Using simplejson.tool from the shell to validate and pretty-print::
$ echo '{"json":"obj"}' | python -m simplejson.tool
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
Expecting property name: line 1 column 3 (char 2)
"""
from __future__ import absolute_import
__version__ = '3.3.0'
__all__ = [
'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
'OrderedDict', 'simple_first',
]
__author__ = 'Bob Ippolito <bob@redivi.com>'
from decimal import Decimal
from .scanner import JSONDecodeError
from .decoder import JSONDecoder
from .encoder import JSONEncoder, JSONEncoderForHTML
def _import_OrderedDict():
import collections
try:
return collections.OrderedDict
except AttributeError:
from . import ordered_dict
return ordered_dict.OrderedDict
OrderedDict = _import_OrderedDict()
def _import_c_make_encoder():
try:
from ._speedups import make_encoder
return make_encoder
except ImportError:
return None
_default_encoder = JSONEncoder(
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
indent=None,
separators=None,
encoding='utf-8',
default=None,
use_decimal=True,
namedtuple_as_object=True,
tuple_as_array=True,
bigint_as_string=False,
item_sort_key=None,
for_json=False,
ignore_nan=False,
)
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True,
bigint_as_string=False, sort_keys=False, item_sort_key=None,
for_json=False, ignore_nan=False, **kw):
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
``.write()``-supporting file-like object).
If *skipkeys* is true then ``dict`` keys that are not basic types
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
will be skipped instead of raising a ``TypeError``.
If *ensure_ascii* is false, then the some chunks written to ``fp``
may be ``unicode`` instances, subject to normal Python ``str`` to
``unicode`` coercion rules. Unless ``fp.write()`` explicitly
understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
to cause an error.
If *check_circular* is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an ``OverflowError`` (or worse).
If *allow_nan* is false, then it will be a ``ValueError`` to
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
in strict compliance of the original JSON specification, instead of using
the JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). See
*ignore_nan* for ECMA-262 compliant behavior.
If *indent* is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
for each level of nesting. ``None`` (the default) selects the most compact
representation without any newlines. For backwards compatibility with
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
If specified, *separators* should be an
``(item_separator, key_separator)`` tuple. The default is ``(', ', ': ')``
if *indent* is ``None`` and ``(',', ': ')`` otherwise. To get the most
compact JSON representation, you should specify ``(',', ':')`` to eliminate
whitespace.
*encoding* is the character encoding for str instances, default is UTF-8.
*default(obj)* is a function that should return a serializable version
of obj or raise ``TypeError``. The default simply raises ``TypeError``.
If *use_decimal* is true (default: ``True``) then decimal.Decimal
will be natively serialized to JSON with full precision.
If *namedtuple_as_object* is true (default: ``True``),
:class:`tuple` subclasses with ``_asdict()`` methods will be encoded
as JSON objects.
If *tuple_as_array* is true (default: ``True``),
:class:`tuple` (and subclasses) will be encoded as JSON arrays.
If *bigint_as_string* is true (default: ``False``), ints 2**53 and higher
or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise. Note that this is still a
lossy operation that will not round-trip correctly and should be used
sparingly.
If specified, *item_sort_key* is a callable used to sort the items in
each dictionary. This is useful if you want to sort items other than
in alphabetical order by key. This option takes precedence over
*sort_keys*.
If *sort_keys* is true (default: ``False``), the output of dictionaries
will be sorted by item.
If *for_json* is true (default: ``False``), objects with a ``for_json()``
method will use the return value of that method for encoding as JSON
instead of the object.
If *ignore_nan* is true (default: ``False``), then out of range
:class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
``null`` in compliance with the ECMA-262 specification. If true, this will
override *allow_nan*.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg. NOTE: You should use *default* or *for_json* instead
of subclassing whenever possible.
"""
# cached encoder
if (not skipkeys and ensure_ascii and
check_circular and allow_nan and
cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and use_decimal
and namedtuple_as_object and tuple_as_array
and not bigint_as_string and not item_sort_key
and not for_json and not ignore_nan and not kw):
iterable = _default_encoder.iterencode(obj)
else:
if cls is None:
cls = JSONEncoder
iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
separators=separators, encoding=encoding,
default=default, use_decimal=use_decimal,
namedtuple_as_object=namedtuple_as_object,
tuple_as_array=tuple_as_array,
bigint_as_string=bigint_as_string,
sort_keys=sort_keys,
item_sort_key=item_sort_key,
for_json=for_json,
ignore_nan=ignore_nan,
**kw).iterencode(obj)
# could accelerate with writelines in some versions of Python, at
# a debuggability cost
for chunk in iterable:
fp.write(chunk)
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True,
bigint_as_string=False, sort_keys=False, item_sort_key=None,
for_json=False, ignore_nan=False, **kw):
"""Serialize ``obj`` to a JSON formatted ``str``.
If ``skipkeys`` is false then ``dict`` keys that are not basic types
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
will be skipped instead of raising a ``TypeError``.
If ``ensure_ascii`` is false, then the return value will be a
``unicode`` instance subject to normal Python ``str`` to ``unicode``
coercion rules instead of being escaped to an ASCII ``str``.
If ``check_circular`` is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an ``OverflowError`` (or worse).
If ``allow_nan`` is false, then it will be a ``ValueError`` to
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
strict compliance of the JSON specification, instead of using the
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
If ``indent`` is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
for each level of nesting. ``None`` (the default) selects the most compact
representation without any newlines. For backwards compatibility with
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
If specified, ``separators`` should be an
``(item_separator, key_separator)`` tuple. The default is ``(', ', ': ')``
if *indent* is ``None`` and ``(',', ': ')`` otherwise. To get the most
compact JSON representation, you should specify ``(',', ':')`` to eliminate
whitespace.
``encoding`` is the character encoding for str instances, default is UTF-8.
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
If *use_decimal* is true (default: ``True``) then decimal.Decimal
will be natively serialized to JSON with full precision.
If *namedtuple_as_object* is true (default: ``True``),
:class:`tuple` subclasses with ``_asdict()`` methods will be encoded
as JSON objects.
If *tuple_as_array* is true (default: ``True``),
:class:`tuple` (and subclasses) will be encoded as JSON arrays.
If *bigint_as_string* is true (not the default), ints 2**53 and higher
or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise.
If specified, *item_sort_key* is a callable used to sort the items in
each dictionary. This is useful if you want to sort items other than
in alphabetical order by key. This option takes precendence over
*sort_keys*.
If *sort_keys* is true (default: ``False``), the output of dictionaries
will be sorted by item.
If *for_json* is true (default: ``False``), objects with a ``for_json()``
method will use the return value of that method for encoding as JSON
instead of the object.
If *ignore_nan* is true (default: ``False``), then out of range
:class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
``null`` in compliance with the ECMA-262 specification. If true, this will
override *allow_nan*.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg. NOTE: You should use *default* instead of subclassing
whenever possible.
"""
# cached encoder
if (not skipkeys and ensure_ascii and
check_circular and allow_nan and
cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and use_decimal
and namedtuple_as_object and tuple_as_array
and not bigint_as_string and not sort_keys
and not item_sort_key and not for_json
and not ignore_nan and not kw):
return _default_encoder.encode(obj)
if cls is None:
cls = JSONEncoder
return cls(
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
separators=separators, encoding=encoding, default=default,
use_decimal=use_decimal,
namedtuple_as_object=namedtuple_as_object,
tuple_as_array=tuple_as_array,
bigint_as_string=bigint_as_string,
sort_keys=sort_keys,
item_sort_key=item_sort_key,
for_json=for_json,
ignore_nan=ignore_nan,
**kw).encode(obj)
_default_decoder = JSONDecoder(encoding=None, object_hook=None,
object_pairs_hook=None)
def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None,
use_decimal=False, namedtuple_as_object=True, tuple_as_array=True,
**kw):
"""Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
a JSON document) to a Python object.
*encoding* determines the encoding used to interpret any
:class:`str` objects decoded by this instance (``'utf-8'`` by
default). It has no effect when decoding :class:`unicode` objects.
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as :class:`unicode`.
*object_hook*, if specified, will be called with the result of every
JSON object decoded and its return value will be used in place of the
given :class:`dict`. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
*object_pairs_hook* is an optional function that will be called with
the result of any object literal decode with an ordered list of pairs.
The return value of *object_pairs_hook* will be used instead of the
:class:`dict`. This feature can be used to implement custom decoders
that rely on the order that the key and value pairs are decoded (for
example, :func:`collections.OrderedDict` will remember the order of
insertion). If *object_hook* is also defined, the *object_pairs_hook*
takes priority.
*parse_float*, if specified, will be called with the string of every
JSON float to be decoded. By default, this is equivalent to
``float(num_str)``. This can be used to use another datatype or parser
for JSON floats (e.g. :class:`decimal.Decimal`).
*parse_int*, if specified, will be called with the string of every
JSON int to be decoded. By default, this is equivalent to
``int(num_str)``. This can be used to use another datatype or parser
for JSON integers (e.g. :class:`float`).
*parse_constant*, if specified, will be called with one of the
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
can be used to raise an exception if invalid JSON numbers are
encountered.
If *use_decimal* is true (default: ``False``) then it implies
parse_float=decimal.Decimal for parity with ``dump``.
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead
of subclassing whenever possible.
"""
return loads(fp.read(),
encoding=encoding, cls=cls, object_hook=object_hook,
parse_float=parse_float, parse_int=parse_int,
parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
use_decimal=use_decimal, **kw)
def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None,
use_decimal=False, **kw):
"""Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
document) to a Python object.
*encoding* determines the encoding used to interpret any
:class:`str` objects decoded by this instance (``'utf-8'`` by
default). It has no effect when decoding :class:`unicode` objects.
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as :class:`unicode`.
*object_hook*, if specified, will be called with the result of every
JSON object decoded and its return value will be used in place of the
given :class:`dict`. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
*object_pairs_hook* is an optional function that will be called with
the result of any object literal decode with an ordered list of pairs.
The return value of *object_pairs_hook* will be used instead of the
:class:`dict`. This feature can be used to implement custom decoders
that rely on the order that the key and value pairs are decoded (for
example, :func:`collections.OrderedDict` will remember the order of
insertion). If *object_hook* is also defined, the *object_pairs_hook*
takes priority.
*parse_float*, if specified, will be called with the string of every
JSON float to be decoded. By default, this is equivalent to
``float(num_str)``. This can be used to use another datatype or parser
for JSON floats (e.g. :class:`decimal.Decimal`).
*parse_int*, if specified, will be called with the string of every
JSON int to be decoded. By default, this is equivalent to
``int(num_str)``. This can be used to use another datatype or parser
for JSON integers (e.g. :class:`float`).
*parse_constant*, if specified, will be called with one of the
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
can be used to raise an exception if invalid JSON numbers are
encountered.
If *use_decimal* is true (default: ``False``) then it implies
parse_float=decimal.Decimal for parity with ``dump``.
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead
of subclassing whenever possible.
"""
if (cls is None and encoding is None and object_hook is None and
parse_int is None and parse_float is None and
parse_constant is None and object_pairs_hook is None
and not use_decimal and not kw):
return _default_decoder.decode(s)
if cls is None:
cls = JSONDecoder
if object_hook is not None:
kw['object_hook'] = object_hook
if object_pairs_hook is not None:
kw['object_pairs_hook'] = object_pairs_hook
if parse_float is not None:
kw['parse_float'] = parse_float
if parse_int is not None:
kw['parse_int'] = parse_int
if parse_constant is not None:
kw['parse_constant'] = parse_constant
if use_decimal:
if parse_float is not None:
raise TypeError("use_decimal=True implies parse_float=Decimal")
kw['parse_float'] = Decimal
return cls(encoding=encoding, **kw).decode(s)
def _toggle_speedups(enabled):
from . import decoder as dec
from . import encoder as enc
from . import scanner as scan
c_make_encoder = _import_c_make_encoder()
if enabled:
dec.scanstring = dec.c_scanstring or dec.py_scanstring
enc.c_make_encoder = c_make_encoder
enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
enc.py_encode_basestring_ascii)
scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
else:
dec.scanstring = dec.py_scanstring
enc.c_make_encoder = None
enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
scan.make_scanner = scan.py_make_scanner
dec.make_scanner = scan.make_scanner
global _default_decoder
_default_decoder = JSONDecoder(
encoding=None,
object_hook=None,
object_pairs_hook=None,
)
global _default_encoder
_default_encoder = JSONEncoder(
skipkeys=False,
ensure_ascii=True,
check_circular=True,
allow_nan=True,
indent=None,
separators=None,
encoding='utf-8',
default=None,
)
def simple_first(kv):
"""Helper function to pass to item_sort_key to sort simple
elements to the top, then container elements.
"""
return (isinstance(kv[1], (list, dict, tuple)), kv[0])

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
"""Python 3 compatibility shims
"""
import sys
if sys.version_info[0] < 3:
PY3 = False
def b(s):
return s
def u(s):
return unicode(s, 'unicode_escape')
import cStringIO as StringIO
StringIO = BytesIO = StringIO.StringIO
text_type = unicode
binary_type = str
string_types = (basestring,)
integer_types = (int, long)
unichr = unichr
reload_module = reload
def fromhex(s):
return s.decode('hex')
else:
PY3 = True
from imp import reload as reload_module
import codecs
def b(s):
return codecs.latin_1_encode(s)[0]
def u(s):
return s
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
text_type = str
binary_type = bytes
string_types = (str,)
integer_types = (int,)
def unichr(s):
return u(chr(s))
def fromhex(s):
return bytes.fromhex(s)
long_type = integer_types[-1]

View File

@@ -1,389 +0,0 @@
"""Implementation of JSONDecoder
"""
from __future__ import absolute_import
import re
import sys
import struct
from .compat import fromhex, b, u, text_type, binary_type, PY3, unichr
from .scanner import make_scanner, JSONDecodeError
def _import_c_scanstring():
try:
from ._speedups import scanstring
return scanstring
except ImportError:
return None
c_scanstring = _import_c_scanstring()
# NOTE (3.1.0): JSONDecodeError may still be imported from this module for
# compatibility, but it was never in the __all__
__all__ = ['JSONDecoder']
FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
def _floatconstants():
_BYTES = fromhex('7FF80000000000007FF0000000000000')
# The struct module in Python 2.4 would get frexp() out of range here
# when an endian is specified in the format string. Fixed in Python 2.5+
if sys.byteorder != 'big':
_BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
nan, inf = struct.unpack('dd', _BYTES)
return nan, inf, -inf
NaN, PosInf, NegInf = _floatconstants()
_CONSTANTS = {
'-Infinity': NegInf,
'Infinity': PosInf,
'NaN': NaN,
}
STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
BACKSLASH = {
'"': u('"'), '\\': u('\u005c'), '/': u('/'),
'b': u('\b'), 'f': u('\f'), 'n': u('\n'), 'r': u('\r'), 't': u('\t'),
}
DEFAULT_ENCODING = "utf-8"
def py_scanstring(s, end, encoding=None, strict=True,
_b=BACKSLASH, _m=STRINGCHUNK.match, _join=u('').join,
_PY3=PY3, _maxunicode=sys.maxunicode):
"""Scan the string s for a JSON string. End is the index of the
character in s after the quote that started the JSON string.
Unescapes all valid JSON string escape sequences and raises ValueError
on attempt to decode an invalid string. If strict is False then literal
control characters are allowed in the string.
Returns a tuple of the decoded string and the index of the character in s
after the end quote."""
if encoding is None:
encoding = DEFAULT_ENCODING
chunks = []
_append = chunks.append
begin = end - 1
while 1:
chunk = _m(s, end)
if chunk is None:
raise JSONDecodeError(
"Unterminated string starting at", s, begin)
end = chunk.end()
content, terminator = chunk.groups()
# Content is contains zero or more unescaped string characters
if content:
if not _PY3 and not isinstance(content, text_type):
content = text_type(content, encoding)
_append(content)
# Terminator is the end of string, a literal control character,
# or a backslash denoting that an escape sequence follows
if terminator == '"':
break
elif terminator != '\\':
if strict:
msg = "Invalid control character %r at"
raise JSONDecodeError(msg, s, end)
else:
_append(terminator)
continue
try:
esc = s[end]
except IndexError:
raise JSONDecodeError(
"Unterminated string starting at", s, begin)
# If not a unicode escape sequence, must be in the lookup table
if esc != 'u':
try:
char = _b[esc]
except KeyError:
msg = "Invalid \\X escape sequence %r"
raise JSONDecodeError(msg, s, end)
end += 1
else:
# Unicode escape sequence
msg = "Invalid \\uXXXX escape sequence"
esc = s[end + 1:end + 5]
escX = esc[1:2]
if len(esc) != 4 or escX == 'x' or escX == 'X':
raise JSONDecodeError(msg, s, end - 1)
try:
uni = int(esc, 16)
except ValueError:
raise JSONDecodeError(msg, s, end - 1)
end += 5
# Check for surrogate pair on UCS-4 systems
# Note that this will join high/low surrogate pairs
# but will also pass unpaired surrogates through
if (_maxunicode > 65535 and
uni & 0xfc00 == 0xd800 and
s[end:end + 2] == '\\u'):
esc2 = s[end + 2:end + 6]
escX = esc2[1:2]
if len(esc2) == 4 and not (escX == 'x' or escX == 'X'):
try:
uni2 = int(esc2, 16)
except ValueError:
raise JSONDecodeError(msg, s, end)
if uni2 & 0xfc00 == 0xdc00:
uni = 0x10000 + (((uni - 0xd800) << 10) |
(uni2 - 0xdc00))
end += 6
char = unichr(uni)
# Append the unescaped character
_append(char)
return _join(chunks), end
# Use speedup if available
scanstring = c_scanstring or py_scanstring
WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
WHITESPACE_STR = ' \t\n\r'
def JSONObject(state, encoding, strict, scan_once, object_hook,
object_pairs_hook, memo=None,
_w=WHITESPACE.match, _ws=WHITESPACE_STR):
(s, end) = state
# Backwards compatibility
if memo is None:
memo = {}
memo_get = memo.setdefault
pairs = []
# Use a slice to prevent IndexError from being raised, the following
# check will raise a more specific ValueError if the string is empty
nextchar = s[end:end + 1]
# Normally we expect nextchar == '"'
if nextchar != '"':
if nextchar in _ws:
end = _w(s, end).end()
nextchar = s[end:end + 1]
# Trivial empty object
if nextchar == '}':
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end + 1
pairs = {}
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end + 1
elif nextchar != '"':
raise JSONDecodeError(
"Expecting property name enclosed in double quotes",
s, end)
end += 1
while True:
key, end = scanstring(s, end, encoding, strict)
key = memo_get(key, key)
# To skip some function call overhead we optimize the fast paths where
# the JSON key separator is ": " or just ":".
if s[end:end + 1] != ':':
end = _w(s, end).end()
if s[end:end + 1] != ':':
raise JSONDecodeError("Expecting ':' delimiter", s, end)
end += 1
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass
value, end = scan_once(s, end)
pairs.append((key, value))
try:
nextchar = s[end]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end]
except IndexError:
nextchar = ''
end += 1
if nextchar == '}':
break
elif nextchar != ',':
raise JSONDecodeError("Expecting ',' delimiter or '}'", s, end - 1)
try:
nextchar = s[end]
if nextchar in _ws:
end += 1
nextchar = s[end]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end]
except IndexError:
nextchar = ''
end += 1
if nextchar != '"':
raise JSONDecodeError(
"Expecting property name enclosed in double quotes",
s, end - 1)
if object_pairs_hook is not None:
result = object_pairs_hook(pairs)
return result, end
pairs = dict(pairs)
if object_hook is not None:
pairs = object_hook(pairs)
return pairs, end
def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
(s, end) = state
values = []
nextchar = s[end:end + 1]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end:end + 1]
# Look-ahead for trivial empty array
if nextchar == ']':
return values, end + 1
elif nextchar == '':
raise JSONDecodeError("Expecting value or ']'", s, end)
_append = values.append
while True:
value, end = scan_once(s, end)
_append(value)
nextchar = s[end:end + 1]
if nextchar in _ws:
end = _w(s, end + 1).end()
nextchar = s[end:end + 1]
end += 1
if nextchar == ']':
break
elif nextchar != ',':
raise JSONDecodeError("Expecting ',' delimiter or ']'", s, end - 1)
try:
if s[end] in _ws:
end += 1
if s[end] in _ws:
end = _w(s, end + 1).end()
except IndexError:
pass
return values, end
class JSONDecoder(object):
"""Simple JSON <http://json.org> decoder
Performs the following translations in decoding by default:
+---------------+-------------------+
| JSON | Python |
+===============+===================+
| object | dict |
+---------------+-------------------+
| array | list |
+---------------+-------------------+
| string | unicode |
+---------------+-------------------+
| number (int) | int, long |
+---------------+-------------------+
| number (real) | float |
+---------------+-------------------+
| true | True |
+---------------+-------------------+
| false | False |
+---------------+-------------------+
| null | None |
+---------------+-------------------+
It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
their corresponding ``float`` values, which is outside the JSON spec.
"""
def __init__(self, encoding=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, strict=True,
object_pairs_hook=None):
"""
*encoding* determines the encoding used to interpret any
:class:`str` objects decoded by this instance (``'utf-8'`` by
default). It has no effect when decoding :class:`unicode` objects.
Note that currently only encodings that are a superset of ASCII work,
strings of other encodings should be passed in as :class:`unicode`.
*object_hook*, if specified, will be called with the result of every
JSON object decoded and its return value will be used in place of the
given :class:`dict`. This can be used to provide custom
deserializations (e.g. to support JSON-RPC class hinting).
*object_pairs_hook* is an optional function that will be called with
the result of any object literal decode with an ordered list of pairs.
The return value of *object_pairs_hook* will be used instead of the
:class:`dict`. This feature can be used to implement custom decoders
that rely on the order that the key and value pairs are decoded (for
example, :func:`collections.OrderedDict` will remember the order of
insertion). If *object_hook* is also defined, the *object_pairs_hook*
takes priority.
*parse_float*, if specified, will be called with the string of every
JSON float to be decoded. By default, this is equivalent to
``float(num_str)``. This can be used to use another datatype or parser
for JSON floats (e.g. :class:`decimal.Decimal`).
*parse_int*, if specified, will be called with the string of every
JSON int to be decoded. By default, this is equivalent to
``int(num_str)``. This can be used to use another datatype or parser
for JSON integers (e.g. :class:`float`).
*parse_constant*, if specified, will be called with one of the
following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
can be used to raise an exception if invalid JSON numbers are
encountered.
*strict* controls the parser's behavior when it encounters an
invalid control character in a string. The default setting of
``True`` means that unescaped control characters are parse errors, if
``False`` then control characters will be allowed in strings.
"""
if encoding is None:
encoding = DEFAULT_ENCODING
self.encoding = encoding
self.object_hook = object_hook
self.object_pairs_hook = object_pairs_hook
self.parse_float = parse_float or float
self.parse_int = parse_int or int
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
self.strict = strict
self.parse_object = JSONObject
self.parse_array = JSONArray
self.parse_string = scanstring
self.memo = {}
self.scan_once = make_scanner(self)
def decode(self, s, _w=WHITESPACE.match, _PY3=PY3):
"""Return the Python representation of ``s`` (a ``str`` or ``unicode``
instance containing a JSON document)
"""
if _PY3 and isinstance(s, binary_type):
s = s.decode(self.encoding)
obj, end = self.raw_decode(s)
end = _w(s, end).end()
if end != len(s):
raise JSONDecodeError("Extra data", s, end, len(s))
return obj
def raw_decode(self, s, idx=0, _w=WHITESPACE.match, _PY3=PY3):
"""Decode a JSON document from ``s`` (a ``str`` or ``unicode``
beginning with a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
Optionally, ``idx`` can be used to specify an offset in ``s`` where
the JSON document begins.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
if _PY3 and not isinstance(s, text_type):
raise TypeError("Input string must be text, not bytes")
return self.scan_once(s, idx=_w(s, idx).end())

View File

@@ -1,628 +0,0 @@
"""Implementation of JSONEncoder
"""
from __future__ import absolute_import
import re
from operator import itemgetter
from decimal import Decimal
from .compat import u, unichr, binary_type, string_types, integer_types, PY3
def _import_speedups():
try:
from . import _speedups
return _speedups.encode_basestring_ascii, _speedups.make_encoder
except ImportError:
return None, None
c_encode_basestring_ascii, c_make_encoder = _import_speedups()
from simplejson.decoder import PosInf
#ESCAPE = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t\u2028\u2029]')
# This is required because u() will mangle the string and ur'' isn't valid
# python3 syntax
ESCAPE = re.compile(u'[\\x00-\\x1f\\\\"\\b\\f\\n\\r\\t\u2028\u2029]')
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
HAS_UTF8 = re.compile(r'[\x80-\xff]')
ESCAPE_DCT = {
'\\': '\\\\',
'"': '\\"',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
}
for i in range(0x20):
#ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
for i in [0x2028, 0x2029]:
ESCAPE_DCT.setdefault(unichr(i), '\\u%04x' % (i,))
FLOAT_REPR = repr
def encode_basestring(s, _PY3=PY3, _q=u('"')):
"""Return a JSON representation of a Python string
"""
if _PY3:
if isinstance(s, binary_type):
s = s.decode('utf-8')
else:
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
s = s.decode('utf-8')
def replace(match):
return ESCAPE_DCT[match.group(0)]
return _q + ESCAPE.sub(replace, s) + _q
def py_encode_basestring_ascii(s, _PY3=PY3):
"""Return an ASCII-only JSON representation of a Python string
"""
if _PY3:
if isinstance(s, binary_type):
s = s.decode('utf-8')
else:
if isinstance(s, str) and HAS_UTF8.search(s) is not None:
s = s.decode('utf-8')
def replace(match):
s = match.group(0)
try:
return ESCAPE_DCT[s]
except KeyError:
n = ord(s)
if n < 0x10000:
#return '\\u{0:04x}'.format(n)
return '\\u%04x' % (n,)
else:
# surrogate pair
n -= 0x10000
s1 = 0xd800 | ((n >> 10) & 0x3ff)
s2 = 0xdc00 | (n & 0x3ff)
#return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
return '\\u%04x\\u%04x' % (s1, s2)
return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
encode_basestring_ascii = (
c_encode_basestring_ascii or py_encode_basestring_ascii)
class JSONEncoder(object):
"""Extensible JSON <http://json.org> encoder for Python data structures.
Supports the following objects and types by default:
+-------------------+---------------+
| Python | JSON |
+===================+===============+
| dict, namedtuple | object |
+-------------------+---------------+
| list, tuple | array |
+-------------------+---------------+
| str, unicode | string |
+-------------------+---------------+
| int, long, float | number |
+-------------------+---------------+
| True | true |
+-------------------+---------------+
| False | false |
+-------------------+---------------+
| None | null |
+-------------------+---------------+
To extend this to recognize other objects, subclass and implement a
``.default()`` method with another method that returns a serializable
object for ``o`` if possible, otherwise it should call the superclass
implementation (to raise ``TypeError``).
"""
item_separator = ', '
key_separator = ': '
def __init__(self, skipkeys=False, ensure_ascii=True,
check_circular=True, allow_nan=True, sort_keys=False,
indent=None, separators=None, encoding='utf-8', default=None,
use_decimal=True, namedtuple_as_object=True,
tuple_as_array=True, bigint_as_string=False,
item_sort_key=None, for_json=False, ignore_nan=False):
"""Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt
encoding of keys that are not str, int, long, float or None. If
skipkeys is True, such items are simply skipped.
If ensure_ascii is true, the output is guaranteed to be str
objects with all incoming unicode characters escaped. If
ensure_ascii is false, the output will be unicode object.
If check_circular is true, then lists, dicts, and custom encoded
objects will be checked for circular references during encoding to
prevent an infinite recursion (which would cause an OverflowError).
Otherwise, no such check takes place.
If allow_nan is true, then NaN, Infinity, and -Infinity will be
encoded as such. This behavior is not JSON specification compliant,
but is consistent with most JavaScript based encoders and decoders.
Otherwise, it will be a ValueError to encode such floats.
If sort_keys is true, then the output of dictionaries will be
sorted by key; this is useful for regression tests to ensure
that JSON serializations can be compared on a day-to-day basis.
If indent is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
for each level of nesting. ``None`` (the default) selects the most compact
representation without any newlines. For backwards compatibility with
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
If specified, separators should be an (item_separator, key_separator)
tuple. The default is (', ', ': ') if *indent* is ``None`` and
(',', ': ') otherwise. To get the most compact JSON representation,
you should specify (',', ':') to eliminate whitespace.
If specified, default is a function that gets called for objects
that can't otherwise be serialized. It should return a JSON encodable
version of the object or raise a ``TypeError``.
If encoding is not None, then all input strings will be
transformed into unicode using that encoding prior to JSON-encoding.
The default is UTF-8.
If use_decimal is true (not the default), ``decimal.Decimal`` will
be supported directly by the encoder. For the inverse, decode JSON
with ``parse_float=decimal.Decimal``.
If namedtuple_as_object is true (the default), objects with
``_asdict()`` methods will be encoded as JSON objects.
If tuple_as_array is true (the default), tuple (and subclasses) will
be encoded as JSON arrays.
If bigint_as_string is true (not the default), ints 2**53 and higher
or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise.
If specified, item_sort_key is a callable used to sort the items in
each dictionary. This is useful if you want to sort items other than
in alphabetical order by key.
If for_json is true (not the default), objects with a ``for_json()``
method will use the return value of that method for encoding as JSON
instead of the object.
If *ignore_nan* is true (default: ``False``), then out of range
:class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized
as ``null`` in compliance with the ECMA-262 specification. If true,
this will override *allow_nan*.
"""
self.skipkeys = skipkeys
self.ensure_ascii = ensure_ascii
self.check_circular = check_circular
self.allow_nan = allow_nan
self.sort_keys = sort_keys
self.use_decimal = use_decimal
self.namedtuple_as_object = namedtuple_as_object
self.tuple_as_array = tuple_as_array
self.bigint_as_string = bigint_as_string
self.item_sort_key = item_sort_key
self.for_json = for_json
self.ignore_nan = ignore_nan
if indent is not None and not isinstance(indent, string_types):
indent = indent * ' '
self.indent = indent
if separators is not None:
self.item_separator, self.key_separator = separators
elif indent is not None:
self.item_separator = ','
if default is not None:
self.default = default
self.encoding = encoding
def default(self, o):
"""Implement this method in a subclass such that it returns
a serializable object for ``o``, or calls the base implementation
(to raise a ``TypeError``).
For example, to support arbitrary iterators, you could
implement default like this::
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, o)
"""
raise TypeError(repr(o) + " is not JSON serializable")
def encode(self, o):
"""Return a JSON string representation of a Python data structure.
>>> from simplejson import JSONEncoder
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
'{"foo": ["bar", "baz"]}'
"""
# This is for extremely simple cases and benchmarks.
if isinstance(o, binary_type):
_encoding = self.encoding
if (_encoding is not None and not (_encoding == 'utf-8')):
o = o.decode(_encoding)
if isinstance(o, string_types):
if self.ensure_ascii:
return encode_basestring_ascii(o)
else:
return encode_basestring(o)
# This doesn't pass the iterator directly to ''.join() because the
# exceptions aren't as detailed. The list call should be roughly
# equivalent to the PySequence_Fast that ''.join() would do.
chunks = self.iterencode(o, _one_shot=True)
if not isinstance(chunks, (list, tuple)):
chunks = list(chunks)
if self.ensure_ascii:
return ''.join(chunks)
else:
return u''.join(chunks)
def iterencode(self, o, _one_shot=False):
"""Encode the given object and yield each string
representation as available.
For example::
for chunk in JSONEncoder().iterencode(bigobject):
mysocket.write(chunk)
"""
if self.check_circular:
markers = {}
else:
markers = None
if self.ensure_ascii:
_encoder = encode_basestring_ascii
else:
_encoder = encode_basestring
if self.encoding != 'utf-8':
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
if isinstance(o, binary_type):
o = o.decode(_encoding)
return _orig_encoder(o)
def floatstr(o, allow_nan=self.allow_nan, ignore_nan=self.ignore_nan,
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
# Check for specials. Note that this type of test is processor
# and/or platform-specific, so do tests which don't depend on
# the internals.
if o != o:
text = 'NaN'
elif o == _inf:
text = 'Infinity'
elif o == _neginf:
text = '-Infinity'
else:
return _repr(o)
if ignore_nan:
text = 'null'
elif not allow_nan:
raise ValueError(
"Out of range float values are not JSON compliant: " +
repr(o))
return text
key_memo = {}
if (_one_shot and c_make_encoder is not None
and self.indent is None):
_iterencode = c_make_encoder(
markers, self.default, _encoder, self.indent,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
self.namedtuple_as_object, self.tuple_as_array,
self.bigint_as_string, self.item_sort_key,
self.encoding, self.for_json, self.ignore_nan,
Decimal)
else:
_iterencode = _make_iterencode(
markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot, self.use_decimal,
self.namedtuple_as_object, self.tuple_as_array,
self.bigint_as_string, self.item_sort_key,
self.encoding, self.for_json,
Decimal=Decimal)
try:
return _iterencode(o, 0)
finally:
key_memo.clear()
class JSONEncoderForHTML(JSONEncoder):
"""An encoder that produces JSON safe to embed in HTML.
To embed JSON content in, say, a script tag on a web page, the
characters &, < and > should be escaped. They cannot be escaped
with the usual entities (e.g. &amp;) because they are not expanded
within <script> tags.
"""
def encode(self, o):
# Override JSONEncoder.encode because it has hacks for
# performance that make things more complicated.
chunks = self.iterencode(o, True)
if self.ensure_ascii:
return ''.join(chunks)
else:
return u''.join(chunks)
def iterencode(self, o, _one_shot=False):
chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
for chunk in chunks:
chunk = chunk.replace('&', '\\u0026')
chunk = chunk.replace('<', '\\u003c')
chunk = chunk.replace('>', '\\u003e')
yield chunk
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
_use_decimal, _namedtuple_as_object, _tuple_as_array,
_bigint_as_string, _item_sort_key, _encoding, _for_json,
## HACK: hand-optimized bytecode; turn globals into locals
_PY3=PY3,
ValueError=ValueError,
string_types=string_types,
Decimal=Decimal,
dict=dict,
float=float,
id=id,
integer_types=integer_types,
isinstance=isinstance,
list=list,
str=str,
tuple=tuple,
):
if _item_sort_key and not callable(_item_sort_key):
raise TypeError("item_sort_key must be None or callable")
elif _sort_keys and not _item_sort_key:
_item_sort_key = itemgetter(0)
def _iterencode_list(lst, _current_indent_level):
if not lst:
yield '[]'
return
if markers is not None:
markerid = id(lst)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = lst
buf = '['
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + (_indent * _current_indent_level)
separator = _item_separator + newline_indent
buf += newline_indent
else:
newline_indent = None
separator = _item_separator
first = True
for value in lst:
if first:
first = False
else:
buf = separator
if (isinstance(value, string_types) or
(_PY3 and isinstance(value, binary_type))):
yield buf + _encoder(value)
elif value is None:
yield buf + 'null'
elif value is True:
yield buf + 'true'
elif value is False:
yield buf + 'false'
elif isinstance(value, integer_types):
yield ((buf + str(value))
if (not _bigint_as_string or
(-1 << 53) < value < (1 << 53))
else (buf + '"' + str(value) + '"'))
elif isinstance(value, float):
yield buf + _floatstr(value)
elif _use_decimal and isinstance(value, Decimal):
yield buf + str(value)
else:
yield buf
for_json = _for_json and getattr(value, 'for_json', None)
if for_json and callable(for_json):
chunks = _iterencode(for_json(), _current_indent_level)
elif isinstance(value, list):
chunks = _iterencode_list(value, _current_indent_level)
else:
_asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
if _asdict and callable(_asdict):
chunks = _iterencode_dict(_asdict(),
_current_indent_level)
elif _tuple_as_array and isinstance(value, tuple):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + (_indent * _current_indent_level)
yield ']'
if markers is not None:
del markers[markerid]
def _stringify_key(key):
if isinstance(key, string_types): # pragma: no cover
pass
elif isinstance(key, binary_type):
key = key.decode(_encoding)
elif isinstance(key, float):
key = _floatstr(key)
elif key is True:
key = 'true'
elif key is False:
key = 'false'
elif key is None:
key = 'null'
elif isinstance(key, integer_types):
key = str(key)
elif _use_decimal and isinstance(key, Decimal):
key = str(key)
elif _skipkeys:
key = None
else:
raise TypeError("key " + repr(key) + " is not a string")
return key
def _iterencode_dict(dct, _current_indent_level):
if not dct:
yield '{}'
return
if markers is not None:
markerid = id(dct)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = dct
yield '{'
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + (_indent * _current_indent_level)
item_separator = _item_separator + newline_indent
yield newline_indent
else:
newline_indent = None
item_separator = _item_separator
first = True
if _PY3:
iteritems = dct.items()
else:
iteritems = dct.iteritems()
if _item_sort_key:
items = []
for k, v in dct.items():
if not isinstance(k, string_types):
k = _stringify_key(k)
if k is None:
continue
items.append((k, v))
items.sort(key=_item_sort_key)
else:
items = iteritems
for key, value in items:
if not (_item_sort_key or isinstance(key, string_types)):
key = _stringify_key(key)
if key is None:
# _skipkeys must be True
continue
if first:
first = False
else:
yield item_separator
yield _encoder(key)
yield _key_separator
if (isinstance(value, string_types) or
(_PY3 and isinstance(value, binary_type))):
yield _encoder(value)
elif value is None:
yield 'null'
elif value is True:
yield 'true'
elif value is False:
yield 'false'
elif isinstance(value, integer_types):
yield (str(value)
if (not _bigint_as_string or
(-1 << 53) < value < (1 << 53))
else ('"' + str(value) + '"'))
elif isinstance(value, float):
yield _floatstr(value)
elif _use_decimal and isinstance(value, Decimal):
yield str(value)
else:
for_json = _for_json and getattr(value, 'for_json', None)
if for_json and callable(for_json):
chunks = _iterencode(for_json(), _current_indent_level)
elif isinstance(value, list):
chunks = _iterencode_list(value, _current_indent_level)
else:
_asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
if _asdict and callable(_asdict):
chunks = _iterencode_dict(_asdict(),
_current_indent_level)
elif _tuple_as_array and isinstance(value, tuple):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + (_indent * _current_indent_level)
yield '}'
if markers is not None:
del markers[markerid]
def _iterencode(o, _current_indent_level):
if (isinstance(o, string_types) or
(_PY3 and isinstance(o, binary_type))):
yield _encoder(o)
elif o is None:
yield 'null'
elif o is True:
yield 'true'
elif o is False:
yield 'false'
elif isinstance(o, integer_types):
yield (str(o)
if (not _bigint_as_string or
(-1 << 53) < o < (1 << 53))
else ('"' + str(o) + '"'))
elif isinstance(o, float):
yield _floatstr(o)
else:
for_json = _for_json and getattr(o, 'for_json', None)
if for_json and callable(for_json):
for chunk in _iterencode(for_json(), _current_indent_level):
yield chunk
elif isinstance(o, list):
for chunk in _iterencode_list(o, _current_indent_level):
yield chunk
else:
_asdict = _namedtuple_as_object and getattr(o, '_asdict', None)
if _asdict and callable(_asdict):
for chunk in _iterencode_dict(_asdict(),
_current_indent_level):
yield chunk
elif (_tuple_as_array and isinstance(o, tuple)):
for chunk in _iterencode_list(o, _current_indent_level):
yield chunk
elif isinstance(o, dict):
for chunk in _iterencode_dict(o, _current_indent_level):
yield chunk
elif _use_decimal and isinstance(o, Decimal):
yield str(o)
else:
if markers is not None:
markerid = id(o)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = o
o = _default(o)
for chunk in _iterencode(o, _current_indent_level):
yield chunk
if markers is not None:
del markers[markerid]
return _iterencode

View File

@@ -1,119 +0,0 @@
"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
http://code.activestate.com/recipes/576693/
"""
from UserDict import DictMixin
# Modified from original to support Python 2.4, see
# http://code.google.com/p/simplejson/issues/detail?id=53
try:
all
except NameError:
def all(seq):
for elem in seq:
if not elem:
return False
return True
class OrderedDict(dict, DictMixin):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__end
except AttributeError:
self.clear()
self.update(*args, **kwds)
def clear(self):
self.__end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.__map = {} # key --> [key, prev, next]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
end = self.__end
curr = end[1]
curr[2] = end[1] = self.__map[key] = [key, curr, end]
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
key, prev, next = self.__map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.__end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.__end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def popitem(self, last=True):
if not self:
raise KeyError('dictionary is empty')
# Modified from original to support Python 2.4, see
# http://code.google.com/p/simplejson/issues/detail?id=53
if last:
key = reversed(self).next()
else:
key = iter(self).next()
value = self.pop(key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
tmp = self.__map, self.__end
del self.__map, self.__end
inst_dict = vars(self).copy()
self.__map, self.__end = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def keys(self):
return list(self)
setdefault = DictMixin.setdefault
update = DictMixin.update
pop = DictMixin.pop
values = DictMixin.values
items = DictMixin.items
iterkeys = DictMixin.iterkeys
itervalues = DictMixin.itervalues
iteritems = DictMixin.iteritems
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
def copy(self):
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
if isinstance(other, OrderedDict):
return len(self)==len(other) and \
all(p==q for p, q in zip(self.items(), other.items()))
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other

View File

@@ -1,125 +0,0 @@
"""JSON token scanner
"""
import re
def _import_c_make_scanner():
try:
from simplejson._speedups import make_scanner
return make_scanner
except ImportError:
return None
c_make_scanner = _import_c_make_scanner()
__all__ = ['make_scanner', 'JSONDecodeError']
NUMBER_RE = re.compile(
r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
(re.VERBOSE | re.MULTILINE | re.DOTALL))
class JSONDecodeError(ValueError):
"""Subclass of ValueError with the following additional properties:
msg: The unformatted error message
doc: The JSON document being parsed
pos: The start index of doc where parsing failed
end: The end index of doc where parsing failed (may be None)
lineno: The line corresponding to pos
colno: The column corresponding to pos
endlineno: The line corresponding to end (may be None)
endcolno: The column corresponding to end (may be None)
"""
# Note that this exception is used from _speedups
def __init__(self, msg, doc, pos, end=None):
ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
self.msg = msg
self.doc = doc
self.pos = pos
self.end = end
self.lineno, self.colno = linecol(doc, pos)
if end is not None:
self.endlineno, self.endcolno = linecol(doc, end)
else:
self.endlineno, self.endcolno = None, None
def linecol(doc, pos):
lineno = doc.count('\n', 0, pos) + 1
if lineno == 1:
colno = pos + 1
else:
colno = pos - doc.rindex('\n', 0, pos)
return lineno, colno
def errmsg(msg, doc, pos, end=None):
lineno, colno = linecol(doc, pos)
msg = msg.replace('%r', repr(doc[pos:pos + 1]))
if end is None:
fmt = '%s: line %d column %d (char %d)'
return fmt % (msg, lineno, colno, pos)
endlineno, endcolno = linecol(doc, end)
fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
def py_make_scanner(context):
parse_object = context.parse_object
parse_array = context.parse_array
parse_string = context.parse_string
match_number = NUMBER_RE.match
encoding = context.encoding
strict = context.strict
parse_float = context.parse_float
parse_int = context.parse_int
parse_constant = context.parse_constant
object_hook = context.object_hook
object_pairs_hook = context.object_pairs_hook
memo = context.memo
def _scan_once(string, idx):
errmsg = 'Expecting value'
try:
nextchar = string[idx]
except IndexError:
raise JSONDecodeError(errmsg, string, idx)
if nextchar == '"':
return parse_string(string, idx + 1, encoding, strict)
elif nextchar == '{':
return parse_object((string, idx + 1), encoding, strict,
_scan_once, object_hook, object_pairs_hook, memo)
elif nextchar == '[':
return parse_array((string, idx + 1), _scan_once)
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
return None, idx + 4
elif nextchar == 't' and string[idx:idx + 4] == 'true':
return True, idx + 4
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
return False, idx + 5
m = match_number(string, idx)
if m is not None:
integer, frac, exp = m.groups()
if frac or exp:
res = parse_float(integer + (frac or '') + (exp or ''))
else:
res = parse_int(integer)
return res, m.end()
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
return parse_constant('NaN'), idx + 3
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
return parse_constant('Infinity'), idx + 8
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
return parse_constant('-Infinity'), idx + 9
else:
raise JSONDecodeError(errmsg, string, idx)
def scan_once(string, idx):
try:
return _scan_once(string, idx)
finally:
memo.clear()
return scan_once
make_scanner = c_make_scanner or py_make_scanner

View File

@@ -1,77 +0,0 @@
from __future__ import absolute_import
import unittest
import doctest
import sys
class OptionalExtensionTestSuite(unittest.TestSuite):
def run(self, result):
import simplejson
run = unittest.TestSuite.run
run(self, result)
if simplejson._import_c_make_encoder() is None:
TestMissingSpeedups().run(result)
else:
simplejson._toggle_speedups(False)
run(self, result)
simplejson._toggle_speedups(True)
return result
class TestMissingSpeedups(unittest.TestCase):
def runTest(self):
if hasattr(sys, 'pypy_translation_info'):
"PyPy doesn't need speedups! :)"
elif hasattr(self, 'skipTest'):
self.skipTest('_speedups.so is missing!')
def additional_tests(suite=None):
import simplejson
import simplejson.encoder
import simplejson.decoder
if suite is None:
suite = unittest.TestSuite()
for mod in (simplejson, simplejson.encoder, simplejson.decoder):
suite.addTest(doctest.DocTestSuite(mod))
suite.addTest(doctest.DocFileSuite('../../index.rst'))
return suite
def all_tests_suite():
suite = unittest.TestLoader().loadTestsFromNames([
'simplejson.tests.test_bigint_as_string',
'simplejson.tests.test_check_circular',
'simplejson.tests.test_decode',
'simplejson.tests.test_default',
'simplejson.tests.test_dump',
'simplejson.tests.test_encode_basestring_ascii',
'simplejson.tests.test_encode_for_html',
'simplejson.tests.test_errors',
'simplejson.tests.test_fail',
'simplejson.tests.test_float',
'simplejson.tests.test_indent',
'simplejson.tests.test_pass1',
'simplejson.tests.test_pass2',
'simplejson.tests.test_pass3',
'simplejson.tests.test_recursion',
'simplejson.tests.test_scanstring',
'simplejson.tests.test_separators',
'simplejson.tests.test_speedups',
'simplejson.tests.test_unicode',
'simplejson.tests.test_decimal',
'simplejson.tests.test_tuple',
'simplejson.tests.test_namedtuple',
])
suite = additional_tests(suite)
return OptionalExtensionTestSuite([suite])
def main():
runner = unittest.TextTestRunner(verbosity=1 + sys.argv.count('-v'))
suite = all_tests_suite()
raise SystemExit(not runner.run(suite).wasSuccessful())
if __name__ == '__main__':
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
main()

View File

@@ -1,58 +0,0 @@
from unittest import TestCase
import simplejson as json
from simplejson.compat import long_type
class TestBigintAsString(TestCase):
# Python 2.5, at least the one that ships on Mac OS X, calculates
# 2 ** 53 as 0! It manages to calculate 1 << 53 correctly.
values = [(200, 200),
((1 << 53) - 1, 9007199254740991),
((1 << 53), '9007199254740992'),
((1 << 53) + 1, '9007199254740993'),
(-100, -100),
((-1 << 53), '-9007199254740992'),
((-1 << 53) - 1, '-9007199254740993'),
((-1 << 53) + 1, -9007199254740991)]
def test_ints(self):
for val, expect in self.values:
self.assertEqual(
val,
json.loads(json.dumps(val)))
self.assertEqual(
expect,
json.loads(json.dumps(val, bigint_as_string=True)))
def test_lists(self):
for val, expect in self.values:
val = [val, val]
expect = [expect, expect]
self.assertEqual(
val,
json.loads(json.dumps(val)))
self.assertEqual(
expect,
json.loads(json.dumps(val, bigint_as_string=True)))
def test_dicts(self):
for val, expect in self.values:
val = {'k': val}
expect = {'k': expect}
self.assertEqual(
val,
json.loads(json.dumps(val)))
self.assertEqual(
expect,
json.loads(json.dumps(val, bigint_as_string=True)))
def test_dict_keys(self):
for val, _ in self.values:
expect = {str(val): 'value'}
val = {val: 'value'}
self.assertEqual(
expect,
json.loads(json.dumps(val)))
self.assertEqual(
expect,
json.loads(json.dumps(val, bigint_as_string=True)))

View File

@@ -1,30 +0,0 @@
from unittest import TestCase
import simplejson as json
def default_iterable(obj):
return list(obj)
class TestCheckCircular(TestCase):
def test_circular_dict(self):
dct = {}
dct['a'] = dct
self.assertRaises(ValueError, json.dumps, dct)
def test_circular_list(self):
lst = []
lst.append(lst)
self.assertRaises(ValueError, json.dumps, lst)
def test_circular_composite(self):
dct2 = {}
dct2['a'] = []
dct2['a'].append(dct2)
self.assertRaises(ValueError, json.dumps, dct2)
def test_circular_default(self):
json.dumps([set()], default=default_iterable)
self.assertRaises(TypeError, json.dumps, [set()])
def test_circular_off_default(self):
json.dumps([set()], default=default_iterable, check_circular=False)
self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)

View File

@@ -1,71 +0,0 @@
import decimal
from decimal import Decimal
from unittest import TestCase
from simplejson.compat import StringIO, reload_module
import simplejson as json
class TestDecimal(TestCase):
NUMS = "1.0", "10.00", "1.1", "1234567890.1234567890", "500"
def dumps(self, obj, **kw):
sio = StringIO()
json.dump(obj, sio, **kw)
res = json.dumps(obj, **kw)
self.assertEqual(res, sio.getvalue())
return res
def loads(self, s, **kw):
sio = StringIO(s)
res = json.loads(s, **kw)
self.assertEqual(res, json.load(sio, **kw))
return res
def test_decimal_encode(self):
for d in map(Decimal, self.NUMS):
self.assertEqual(self.dumps(d, use_decimal=True), str(d))
def test_decimal_decode(self):
for s in self.NUMS:
self.assertEqual(self.loads(s, parse_float=Decimal), Decimal(s))
def test_stringify_key(self):
for d in map(Decimal, self.NUMS):
v = {d: d}
self.assertEqual(
self.loads(
self.dumps(v, use_decimal=True), parse_float=Decimal),
{str(d): d})
def test_decimal_roundtrip(self):
for d in map(Decimal, self.NUMS):
# The type might not be the same (int and Decimal) but they
# should still compare equal.
for v in [d, [d], {'': d}]:
self.assertEqual(
self.loads(
self.dumps(v, use_decimal=True), parse_float=Decimal),
v)
def test_decimal_defaults(self):
d = Decimal('1.1')
# use_decimal=True is the default
self.assertRaises(TypeError, json.dumps, d, use_decimal=False)
self.assertEqual('1.1', json.dumps(d))
self.assertEqual('1.1', json.dumps(d, use_decimal=True))
self.assertRaises(TypeError, json.dump, d, StringIO(),
use_decimal=False)
sio = StringIO()
json.dump(d, sio)
self.assertEqual('1.1', sio.getvalue())
sio = StringIO()
json.dump(d, sio, use_decimal=True)
self.assertEqual('1.1', sio.getvalue())
def test_decimal_reload(self):
# Simulate a subinterpreter that reloads the Python modules but not
# the C code https://github.com/simplejson/simplejson/issues/34
global Decimal
Decimal = reload_module(decimal).Decimal
import simplejson.encoder
simplejson.encoder.Decimal = Decimal
self.test_decimal_roundtrip()

View File

@@ -1,88 +0,0 @@
from __future__ import absolute_import
import decimal
from unittest import TestCase
import simplejson as json
from simplejson.compat import StringIO
from simplejson import OrderedDict
class TestDecode(TestCase):
if not hasattr(TestCase, 'assertIs'):
def assertIs(self, a, b):
self.assertTrue(a is b, '%r is %r' % (a, b))
def test_decimal(self):
rval = json.loads('1.1', parse_float=decimal.Decimal)
self.assertTrue(isinstance(rval, decimal.Decimal))
self.assertEqual(rval, decimal.Decimal('1.1'))
def test_float(self):
rval = json.loads('1', parse_int=float)
self.assertTrue(isinstance(rval, float))
self.assertEqual(rval, 1.0)
def test_decoder_optimizations(self):
# Several optimizations were made that skip over calls to
# the whitespace regex, so this test is designed to try and
# exercise the uncommon cases. The array cases are already covered.
rval = json.loads('{ "key" : "value" , "k":"v" }')
self.assertEqual(rval, {"key":"value", "k":"v"})
def test_empty_objects(self):
s = '{}'
self.assertEqual(json.loads(s), eval(s))
s = '[]'
self.assertEqual(json.loads(s), eval(s))
s = '""'
self.assertEqual(json.loads(s), eval(s))
def test_object_pairs_hook(self):
s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4),
("qrt", 5), ("pad", 6), ("hoy", 7)]
self.assertEqual(json.loads(s), eval(s))
self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
self.assertEqual(json.load(StringIO(s),
object_pairs_hook=lambda x: x), p)
od = json.loads(s, object_pairs_hook=OrderedDict)
self.assertEqual(od, OrderedDict(p))
self.assertEqual(type(od), OrderedDict)
# the object_pairs_hook takes priority over the object_hook
self.assertEqual(json.loads(s,
object_pairs_hook=OrderedDict,
object_hook=lambda x: None),
OrderedDict(p))
def check_keys_reuse(self, source, loads):
rval = loads(source)
(a, b), (c, d) = sorted(rval[0]), sorted(rval[1])
self.assertIs(a, c)
self.assertIs(b, d)
def test_keys_reuse_str(self):
s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'.encode('utf8')
self.check_keys_reuse(s, json.loads)
def test_keys_reuse_unicode(self):
s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'
self.check_keys_reuse(s, json.loads)
def test_empty_strings(self):
self.assertEqual(json.loads('""'), "")
self.assertEqual(json.loads(u'""'), u"")
self.assertEqual(json.loads('[""]'), [""])
self.assertEqual(json.loads(u'[""]'), [u""])
def test_raw_decode(self):
cls = json.decoder.JSONDecoder
self.assertEqual(
({'a': {}}, 9),
cls().raw_decode("{\"a\": {}}"))
# http://code.google.com/p/simplejson/issues/detail?id=85
self.assertEqual(
({'a': {}}, 9),
cls(object_pairs_hook=dict).raw_decode("{\"a\": {}}"))
# https://github.com/simplejson/simplejson/pull/38
self.assertEqual(
({'a': {}}, 11),
cls().raw_decode(" \n{\"a\": {}}"))

View File

@@ -1,9 +0,0 @@
from unittest import TestCase
import simplejson as json
class TestDefault(TestCase):
def test_default(self):
self.assertEqual(
json.dumps(type, default=repr),
json.dumps(repr(type)))

View File

@@ -1,121 +0,0 @@
from unittest import TestCase
from simplejson.compat import StringIO, long_type, b, binary_type, PY3
import simplejson as json
def as_text_type(s):
if PY3 and isinstance(s, binary_type):
return s.decode('ascii')
return s
class TestDump(TestCase):
def test_dump(self):
sio = StringIO()
json.dump({}, sio)
self.assertEqual(sio.getvalue(), '{}')
def test_constants(self):
for c in [None, True, False]:
self.assertTrue(json.loads(json.dumps(c)) is c)
self.assertTrue(json.loads(json.dumps([c]))[0] is c)
self.assertTrue(json.loads(json.dumps({'a': c}))['a'] is c)
def test_stringify_key(self):
items = [(b('bytes'), 'bytes'),
(1.0, '1.0'),
(10, '10'),
(True, 'true'),
(False, 'false'),
(None, 'null'),
(long_type(100), '100')]
for k, expect in items:
self.assertEqual(
json.loads(json.dumps({k: expect})),
{expect: expect})
self.assertEqual(
json.loads(json.dumps({k: expect}, sort_keys=True)),
{expect: expect})
self.assertRaises(TypeError, json.dumps, {json: 1})
for v in [{}, {'other': 1}, {b('derp'): 1, 'herp': 2}]:
for sort_keys in [False, True]:
v0 = dict(v)
v0[json] = 1
v1 = dict((as_text_type(key), val) for (key, val) in v.items())
self.assertEqual(
json.loads(json.dumps(v0, skipkeys=True, sort_keys=sort_keys)),
v1)
self.assertEqual(
json.loads(json.dumps({'': v0}, skipkeys=True, sort_keys=sort_keys)),
{'': v1})
self.assertEqual(
json.loads(json.dumps([v0], skipkeys=True, sort_keys=sort_keys)),
[v1])
def test_dumps(self):
self.assertEqual(json.dumps({}), '{}')
def test_encode_truefalse(self):
self.assertEqual(json.dumps(
{True: False, False: True}, sort_keys=True),
'{"false": true, "true": false}')
self.assertEqual(
json.dumps(
{2: 3.0,
4.0: long_type(5),
False: 1,
long_type(6): True,
"7": 0},
sort_keys=True),
'{"2": 3.0, "4.0": 5, "6": true, "7": 0, "false": 1}')
def test_ordered_dict(self):
# http://bugs.python.org/issue6105
items = [('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
s = json.dumps(json.OrderedDict(items))
self.assertEqual(
s,
'{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5}')
def test_indent_unknown_type_acceptance(self):
"""
A test against the regression mentioned at `github issue 29`_.
The indent parameter should accept any type which pretends to be
an instance of int or long when it comes to being multiplied by
strings, even if it is not actually an int or long, for
backwards compatibility.
.. _github issue 29:
http://github.com/simplejson/simplejson/issue/29
"""
class AwesomeInt(object):
"""An awesome reimplementation of integers"""
def __init__(self, *args, **kwargs):
if len(args) > 0:
# [construct from literals, objects, etc.]
# ...
# Finally, if args[0] is an integer, store it
if isinstance(args[0], int):
self._int = args[0]
# [various methods]
def __mul__(self, other):
# [various ways to multiply AwesomeInt objects]
# ... finally, if the right-hand operand is not awesome enough,
# try to do a normal integer multiplication
if hasattr(self, '_int'):
return self._int * other
else:
raise NotImplementedError("To do non-awesome things with"
" this object, please construct it from an integer!")
s = json.dumps([0, 1, 2], indent=AwesomeInt(3))
self.assertEqual(s, '[\n 0,\n 1,\n 2\n]')
def test_accumulator(self):
# the C API uses an accumulator that collects after 100,000 appends
lst = [0] * 100000
self.assertEqual(json.loads(json.dumps(lst)), lst)

View File

@@ -1,47 +0,0 @@
from unittest import TestCase
import simplejson.encoder
from simplejson.compat import b
CASES = [
(u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
(u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
(u'controls', '"controls"'),
(u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
(u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
(u' s p a c e d ', '" s p a c e d "'),
(u'\U0001d120', '"\\ud834\\udd20"'),
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
(b('\xce\xb1\xce\xa9'), '"\\u03b1\\u03a9"'),
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
(b('\xce\xb1\xce\xa9'), '"\\u03b1\\u03a9"'),
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
(u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
(u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
(u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
(u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
]
class TestEncodeBaseStringAscii(TestCase):
def test_py_encode_basestring_ascii(self):
self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
def test_c_encode_basestring_ascii(self):
if not simplejson.encoder.c_encode_basestring_ascii:
return
self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
def _test_encode_basestring_ascii(self, encode_basestring_ascii):
fname = encode_basestring_ascii.__name__
for input_string, expect in CASES:
result = encode_basestring_ascii(input_string)
#self.assertEqual(result, expect,
# '{0!r} != {1!r} for {2}({3!r})'.format(
# result, expect, fname, input_string))
self.assertEqual(result, expect,
'%r != %r for %s(%r)' % (result, expect, fname, input_string))
def test_sorted_dict(self):
items = [('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
s = simplejson.dumps(dict(items), sort_keys=True)
self.assertEqual(s, '{"five": 5, "four": 4, "one": 1, "three": 3, "two": 2}')

View File

@@ -1,30 +0,0 @@
import unittest
import simplejson as json
class TestEncodeForHTML(unittest.TestCase):
def setUp(self):
self.decoder = json.JSONDecoder()
self.encoder = json.JSONEncoderForHTML()
def test_basic_encode(self):
self.assertEqual(r'"\u0026"', self.encoder.encode('&'))
self.assertEqual(r'"\u003c"', self.encoder.encode('<'))
self.assertEqual(r'"\u003e"', self.encoder.encode('>'))
def test_basic_roundtrip(self):
for char in '&<>':
self.assertEqual(
char, self.decoder.decode(
self.encoder.encode(char)))
def test_prevent_script_breakout(self):
bad_string = '</script><script>alert("gotcha")</script>'
self.assertEqual(
r'"\u003c/script\u003e\u003cscript\u003e'
r'alert(\"gotcha\")\u003c/script\u003e"',
self.encoder.encode(bad_string))
self.assertEqual(
bad_string, self.decoder.decode(
self.encoder.encode(bad_string)))

View File

@@ -1,35 +0,0 @@
import sys
from unittest import TestCase
import simplejson as json
from simplejson.compat import u, b
class TestErrors(TestCase):
def test_string_keys_error(self):
data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]
self.assertRaises(TypeError, json.dumps, data)
def test_decode_error(self):
err = None
try:
json.loads('{}\na\nb')
except json.JSONDecodeError:
err = sys.exc_info()[1]
else:
self.fail('Expected JSONDecodeError')
self.assertEqual(err.lineno, 2)
self.assertEqual(err.colno, 1)
self.assertEqual(err.endlineno, 3)
self.assertEqual(err.endcolno, 2)
def test_scan_error(self):
err = None
for t in (u, b):
try:
json.loads(t('{"asdf": "'))
except json.JSONDecodeError:
err = sys.exc_info()[1]
else:
self.fail('Expected JSONDecodeError')
self.assertEqual(err.lineno, 1)
self.assertEqual(err.colno, 9)

View File

@@ -1,119 +0,0 @@
import sys
from unittest import TestCase
import simplejson as json
# Fri Dec 30 18:57:26 2005
JSONDOCS = [
# http://json.org/JSON_checker/test/fail1.json
'"A JSON payload should be an object or array, not a string."',
# http://json.org/JSON_checker/test/fail2.json
'["Unclosed array"',
# http://json.org/JSON_checker/test/fail3.json
'{unquoted_key: "keys must be quoted}',
# http://json.org/JSON_checker/test/fail4.json
'["extra comma",]',
# http://json.org/JSON_checker/test/fail5.json
'["double extra comma",,]',
# http://json.org/JSON_checker/test/fail6.json
'[ , "<-- missing value"]',
# http://json.org/JSON_checker/test/fail7.json
'["Comma after the close"],',
# http://json.org/JSON_checker/test/fail8.json
'["Extra close"]]',
# http://json.org/JSON_checker/test/fail9.json
'{"Extra comma": true,}',
# http://json.org/JSON_checker/test/fail10.json
'{"Extra value after close": true} "misplaced quoted value"',
# http://json.org/JSON_checker/test/fail11.json
'{"Illegal expression": 1 + 2}',
# http://json.org/JSON_checker/test/fail12.json
'{"Illegal invocation": alert()}',
# http://json.org/JSON_checker/test/fail13.json
'{"Numbers cannot have leading zeroes": 013}',
# http://json.org/JSON_checker/test/fail14.json
'{"Numbers cannot be hex": 0x14}',
# http://json.org/JSON_checker/test/fail15.json
'["Illegal backslash escape: \\x15"]',
# http://json.org/JSON_checker/test/fail16.json
'["Illegal backslash escape: \\\'"]',
# http://json.org/JSON_checker/test/fail17.json
'["Illegal backslash escape: \\017"]',
# http://json.org/JSON_checker/test/fail18.json
'[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
# http://json.org/JSON_checker/test/fail19.json
'{"Missing colon" null}',
# http://json.org/JSON_checker/test/fail20.json
'{"Double colon":: null}',
# http://json.org/JSON_checker/test/fail21.json
'{"Comma instead of colon", null}',
# http://json.org/JSON_checker/test/fail22.json
'["Colon instead of comma": false]',
# http://json.org/JSON_checker/test/fail23.json
'["Bad value", truth]',
# http://json.org/JSON_checker/test/fail24.json
"['single quote']",
# http://code.google.com/p/simplejson/issues/detail?id=3
u'["A\u001FZ control characters in string"]',
# misc based on coverage
'{',
'{]',
'{"foo": "bar"]',
'{"foo": "bar"',
'nul',
'nulx',
'-',
'-x',
'-e',
'-e0',
'-Infinite',
'-Inf',
'Infinit',
'Infinite',
'NaM',
'NuN',
'falsy',
'fal',
'trug',
'tru',
'1e',
'1ex',
'1e-',
'1e-x',
]
SKIPS = {
1: "why not have a string payload?",
18: "spec doesn't specify any nesting limitations",
}
class TestFail(TestCase):
def test_failures(self):
for idx, doc in enumerate(JSONDOCS):
idx = idx + 1
if idx in SKIPS:
json.loads(doc)
continue
try:
json.loads(doc)
except json.JSONDecodeError:
pass
else:
#self.fail("Expected failure for fail{0}.json: {1!r}".format(idx, doc))
self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
def test_array_decoder_issue46(self):
# http://code.google.com/p/simplejson/issues/detail?id=46
for doc in [u'[,]', '[,]']:
try:
json.loads(doc)
except json.JSONDecodeError:
e = sys.exc_info()[1]
self.assertEqual(e.pos, 1)
self.assertEqual(e.lineno, 1)
self.assertEqual(e.colno, 1)
except Exception:
e = sys.exc_info()[1]
self.fail("Unexpected exception raised %r %s" % (e, e))
else:
self.fail("Unexpected success parsing '[,]'")

View File

@@ -1,27 +0,0 @@
import math
from unittest import TestCase
from simplejson.compat import long_type, text_type
import simplejson as json
from simplejson.decoder import NaN, PosInf, NegInf
class TestFloat(TestCase):
def test_degenerates(self):
for inf in (PosInf, NegInf):
self.assertEqual(json.loads(json.dumps(inf)), inf)
# Python 2.5 doesn't have math.isnan
nan = json.loads(json.dumps(NaN))
self.assertTrue((0 + nan) != nan)
def test_floats(self):
for num in [1617161771.7650001, math.pi, math.pi**100,
math.pi**-100, 3.1]:
self.assertEqual(float(json.dumps(num)), num)
self.assertEqual(json.loads(json.dumps(num)), num)
self.assertEqual(json.loads(text_type(json.dumps(num))), num)
def test_ints(self):
for num in [1, long_type(1), 1<<32, 1<<64]:
self.assertEqual(json.dumps(num), str(num))
self.assertEqual(int(json.dumps(num)), num)
self.assertEqual(json.loads(json.dumps(num)), num)
self.assertEqual(json.loads(text_type(json.dumps(num))), num)

View File

@@ -1,86 +0,0 @@
from unittest import TestCase
import textwrap
import simplejson as json
from simplejson.compat import StringIO
class TestIndent(TestCase):
def test_indent(self):
h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh',
'i-vhbjkhnth',
{'nifty': 87}, {'field': 'yes', 'morefield': False} ]
expect = textwrap.dedent("""\
[
\t[
\t\t"blorpie"
\t],
\t[
\t\t"whoops"
\t],
\t[],
\t"d-shtaeou",
\t"d-nthiouh",
\t"i-vhbjkhnth",
\t{
\t\t"nifty": 87
\t},
\t{
\t\t"field": "yes",
\t\t"morefield": false
\t}
]""")
d1 = json.dumps(h)
d2 = json.dumps(h, indent='\t', sort_keys=True, separators=(',', ': '))
d3 = json.dumps(h, indent=' ', sort_keys=True, separators=(',', ': '))
d4 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
h1 = json.loads(d1)
h2 = json.loads(d2)
h3 = json.loads(d3)
h4 = json.loads(d4)
self.assertEqual(h1, h)
self.assertEqual(h2, h)
self.assertEqual(h3, h)
self.assertEqual(h4, h)
self.assertEqual(d3, expect.replace('\t', ' '))
self.assertEqual(d4, expect.replace('\t', ' '))
# NOTE: Python 2.4 textwrap.dedent converts tabs to spaces,
# so the following is expected to fail. Python 2.4 is not a
# supported platform in simplejson 2.1.0+.
self.assertEqual(d2, expect)
def test_indent0(self):
h = {3: 1}
def check(indent, expected):
d1 = json.dumps(h, indent=indent)
self.assertEqual(d1, expected)
sio = StringIO()
json.dump(h, sio, indent=indent)
self.assertEqual(sio.getvalue(), expected)
# indent=0 should emit newlines
check(0, '{\n"3": 1\n}')
# indent=None is more compact
check(None, '{"3": 1}')
def test_separators(self):
lst = [1,2,3,4]
expect = '[\n1,\n2,\n3,\n4\n]'
expect_spaces = '[\n1, \n2, \n3, \n4\n]'
# Ensure that separators still works
self.assertEqual(
expect_spaces,
json.dumps(lst, indent=0, separators=(', ', ': ')))
# Force the new defaults
self.assertEqual(
expect,
json.dumps(lst, indent=0, separators=(',', ': ')))
# Added in 2.1.4
self.assertEqual(
expect,
json.dumps(lst, indent=0))

View File

@@ -1,20 +0,0 @@
from unittest import TestCase
import simplejson as json
from operator import itemgetter
class TestItemSortKey(TestCase):
def test_simple_first(self):
a = {'a': 1, 'c': 5, 'jack': 'jill', 'pick': 'axe', 'array': [1, 5, 6, 9], 'tuple': (83, 12, 3), 'crate': 'dog', 'zeak': 'oh'}
self.assertEqual(
'{"a": 1, "c": 5, "crate": "dog", "jack": "jill", "pick": "axe", "zeak": "oh", "array": [1, 5, 6, 9], "tuple": [83, 12, 3]}',
json.dumps(a, item_sort_key=json.simple_first))
def test_case(self):
a = {'a': 1, 'c': 5, 'Jack': 'jill', 'pick': 'axe', 'Array': [1, 5, 6, 9], 'tuple': (83, 12, 3), 'crate': 'dog', 'zeak': 'oh'}
self.assertEqual(
'{"Array": [1, 5, 6, 9], "Jack": "jill", "a": 1, "c": 5, "crate": "dog", "pick": "axe", "tuple": [83, 12, 3], "zeak": "oh"}',
json.dumps(a, item_sort_key=itemgetter(0)))
self.assertEqual(
'{"a": 1, "Array": [1, 5, 6, 9], "c": 5, "crate": "dog", "Jack": "jill", "pick": "axe", "tuple": [83, 12, 3], "zeak": "oh"}',
json.dumps(a, item_sort_key=lambda kv: kv[0].lower()))

View File

@@ -1,122 +0,0 @@
from __future__ import absolute_import
import unittest
import simplejson as json
from simplejson.compat import StringIO
try:
from collections import namedtuple
except ImportError:
class Value(tuple):
def __new__(cls, *args):
return tuple.__new__(cls, args)
def _asdict(self):
return {'value': self[0]}
class Point(tuple):
def __new__(cls, *args):
return tuple.__new__(cls, args)
def _asdict(self):
return {'x': self[0], 'y': self[1]}
else:
Value = namedtuple('Value', ['value'])
Point = namedtuple('Point', ['x', 'y'])
class DuckValue(object):
def __init__(self, *args):
self.value = Value(*args)
def _asdict(self):
return self.value._asdict()
class DuckPoint(object):
def __init__(self, *args):
self.point = Point(*args)
def _asdict(self):
return self.point._asdict()
class DeadDuck(object):
_asdict = None
class DeadDict(dict):
_asdict = None
CONSTRUCTORS = [
lambda v: v,
lambda v: [v],
lambda v: [{'key': v}],
]
class TestNamedTuple(unittest.TestCase):
def test_namedtuple_dumps(self):
for v in [Value(1), Point(1, 2), DuckValue(1), DuckPoint(1, 2)]:
d = v._asdict()
self.assertEqual(d, json.loads(json.dumps(v)))
self.assertEqual(
d,
json.loads(json.dumps(v, namedtuple_as_object=True)))
self.assertEqual(d, json.loads(json.dumps(v, tuple_as_array=False)))
self.assertEqual(
d,
json.loads(json.dumps(v, namedtuple_as_object=True,
tuple_as_array=False)))
def test_namedtuple_dumps_false(self):
for v in [Value(1), Point(1, 2)]:
l = list(v)
self.assertEqual(
l,
json.loads(json.dumps(v, namedtuple_as_object=False)))
self.assertRaises(TypeError, json.dumps, v,
tuple_as_array=False, namedtuple_as_object=False)
def test_namedtuple_dump(self):
for v in [Value(1), Point(1, 2), DuckValue(1), DuckPoint(1, 2)]:
d = v._asdict()
sio = StringIO()
json.dump(v, sio)
self.assertEqual(d, json.loads(sio.getvalue()))
sio = StringIO()
json.dump(v, sio, namedtuple_as_object=True)
self.assertEqual(
d,
json.loads(sio.getvalue()))
sio = StringIO()
json.dump(v, sio, tuple_as_array=False)
self.assertEqual(d, json.loads(sio.getvalue()))
sio = StringIO()
json.dump(v, sio, namedtuple_as_object=True,
tuple_as_array=False)
self.assertEqual(
d,
json.loads(sio.getvalue()))
def test_namedtuple_dump_false(self):
for v in [Value(1), Point(1, 2)]:
l = list(v)
sio = StringIO()
json.dump(v, sio, namedtuple_as_object=False)
self.assertEqual(
l,
json.loads(sio.getvalue()))
self.assertRaises(TypeError, json.dump, v, StringIO(),
tuple_as_array=False, namedtuple_as_object=False)
def test_asdict_not_callable_dump(self):
for f in CONSTRUCTORS:
self.assertRaises(TypeError,
json.dump, f(DeadDuck()), StringIO(), namedtuple_as_object=True)
sio = StringIO()
json.dump(f(DeadDict()), sio, namedtuple_as_object=True)
self.assertEqual(
json.dumps(f({})),
sio.getvalue())
def test_asdict_not_callable_dumps(self):
for f in CONSTRUCTORS:
self.assertRaises(TypeError,
json.dumps, f(DeadDuck()), namedtuple_as_object=True)
self.assertEqual(
json.dumps(f({})),
json.dumps(f(DeadDict()), namedtuple_as_object=True))

View File

@@ -1,76 +0,0 @@
from unittest import TestCase
import simplejson as json
# from http://json.org/JSON_checker/test/pass1.json
JSON = r'''
[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E666,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\"",
"backslash": "\\",
"controls": "\b\f\n\r\t",
"slash": "/ & \/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],
"compact": [1,2,3,4,5,6,7],
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
"quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066
,"rosebud"]
'''
class TestPass1(TestCase):
def test_parse(self):
# test in/out equivalence and parsing
res = json.loads(JSON)
out = json.dumps(res)
self.assertEqual(res, json.loads(out))
try:
json.dumps(res, allow_nan=False)
except ValueError:
pass
else:
self.fail("23456789012E666 should be out of range")

View File

@@ -1,14 +0,0 @@
from unittest import TestCase
import simplejson as json
# from http://json.org/JSON_checker/test/pass2.json
JSON = r'''
[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
'''
class TestPass2(TestCase):
def test_parse(self):
# test in/out equivalence and parsing
res = json.loads(JSON)
out = json.dumps(res)
self.assertEqual(res, json.loads(out))

View File

@@ -1,20 +0,0 @@
from unittest import TestCase
import simplejson as json
# from http://json.org/JSON_checker/test/pass3.json
JSON = r'''
{
"JSON Test Pattern pass3": {
"The outermost value": "must be an object or array.",
"In this test": "It is an object."
}
}
'''
class TestPass3(TestCase):
def test_parse(self):
# test in/out equivalence and parsing
res = json.loads(JSON)
out = json.dumps(res)
self.assertEqual(res, json.loads(out))

View File

@@ -1,67 +0,0 @@
from unittest import TestCase
import simplejson as json
class JSONTestObject:
pass
class RecursiveJSONEncoder(json.JSONEncoder):
recurse = False
def default(self, o):
if o is JSONTestObject:
if self.recurse:
return [JSONTestObject]
else:
return 'JSONTestObject'
return json.JSONEncoder.default(o)
class TestRecursion(TestCase):
def test_listrecursion(self):
x = []
x.append(x)
try:
json.dumps(x)
except ValueError:
pass
else:
self.fail("didn't raise ValueError on list recursion")
x = []
y = [x]
x.append(y)
try:
json.dumps(x)
except ValueError:
pass
else:
self.fail("didn't raise ValueError on alternating list recursion")
y = []
x = [y, y]
# ensure that the marker is cleared
json.dumps(x)
def test_dictrecursion(self):
x = {}
x["test"] = x
try:
json.dumps(x)
except ValueError:
pass
else:
self.fail("didn't raise ValueError on dict recursion")
x = {}
y = {"a": x, "b": x}
# ensure that the marker is cleared
json.dumps(y)
def test_defaultrecursion(self):
enc = RecursiveJSONEncoder()
self.assertEqual(enc.encode(JSONTestObject), '"JSONTestObject"')
enc.recurse = True
try:
enc.encode(JSONTestObject)
except ValueError:
pass
else:
self.fail("didn't raise ValueError on default recursion")

View File

@@ -1,147 +0,0 @@
import sys
from unittest import TestCase
import simplejson as json
import simplejson.decoder
from simplejson.compat import b, PY3
class TestScanString(TestCase):
# The bytes type is intentionally not used in most of these tests
# under Python 3 because the decoder immediately coerces to str before
# calling scanstring. In Python 2 we are testing the code paths
# for both unicode and str.
#
# The reason this is done is because Python 3 would require
# entirely different code paths for parsing bytes and str.
#
def test_py_scanstring(self):
self._test_scanstring(simplejson.decoder.py_scanstring)
def test_c_scanstring(self):
if not simplejson.decoder.c_scanstring:
return
self._test_scanstring(simplejson.decoder.c_scanstring)
def _test_scanstring(self, scanstring):
self.assertEqual(
scanstring('"z\\ud834\\udd20x"', 1, None, True),
(u'z\U0001d120x', 16))
if sys.maxunicode == 65535:
self.assertEqual(
scanstring(u'"z\U0001d120x"', 1, None, True),
(u'z\U0001d120x', 6))
else:
self.assertEqual(
scanstring(u'"z\U0001d120x"', 1, None, True),
(u'z\U0001d120x', 5))
self.assertEqual(
scanstring('"\\u007b"', 1, None, True),
(u'{', 8))
self.assertEqual(
scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
(u'A JSON payload should be an object or array, not a string.', 60))
self.assertEqual(
scanstring('["Unclosed array"', 2, None, True),
(u'Unclosed array', 17))
self.assertEqual(
scanstring('["extra comma",]', 2, None, True),
(u'extra comma', 14))
self.assertEqual(
scanstring('["double extra comma",,]', 2, None, True),
(u'double extra comma', 21))
self.assertEqual(
scanstring('["Comma after the close"],', 2, None, True),
(u'Comma after the close', 24))
self.assertEqual(
scanstring('["Extra close"]]', 2, None, True),
(u'Extra close', 14))
self.assertEqual(
scanstring('{"Extra comma": true,}', 2, None, True),
(u'Extra comma', 14))
self.assertEqual(
scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
(u'Extra value after close', 26))
self.assertEqual(
scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
(u'Illegal expression', 21))
self.assertEqual(
scanstring('{"Illegal invocation": alert()}', 2, None, True),
(u'Illegal invocation', 21))
self.assertEqual(
scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
(u'Numbers cannot have leading zeroes', 37))
self.assertEqual(
scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
(u'Numbers cannot be hex', 24))
self.assertEqual(
scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
(u'Too deep', 30))
self.assertEqual(
scanstring('{"Missing colon" null}', 2, None, True),
(u'Missing colon', 16))
self.assertEqual(
scanstring('{"Double colon":: null}', 2, None, True),
(u'Double colon', 15))
self.assertEqual(
scanstring('{"Comma instead of colon", null}', 2, None, True),
(u'Comma instead of colon', 25))
self.assertEqual(
scanstring('["Colon instead of comma": false]', 2, None, True),
(u'Colon instead of comma', 25))
self.assertEqual(
scanstring('["Bad value", truth]', 2, None, True),
(u'Bad value', 12))
for c in map(chr, range(0x00, 0x1f)):
self.assertEqual(
scanstring(c + '"', 0, None, False),
(c, 2))
self.assertRaises(
ValueError,
scanstring, c + '"', 0, None, True)
self.assertRaises(ValueError, scanstring, '', 0, None, True)
self.assertRaises(ValueError, scanstring, 'a', 0, None, True)
self.assertRaises(ValueError, scanstring, '\\', 0, None, True)
self.assertRaises(ValueError, scanstring, '\\u', 0, None, True)
self.assertRaises(ValueError, scanstring, '\\u0', 0, None, True)
self.assertRaises(ValueError, scanstring, '\\u01', 0, None, True)
self.assertRaises(ValueError, scanstring, '\\u012', 0, None, True)
self.assertRaises(ValueError, scanstring, '\\u0123', 0, None, True)
if sys.maxunicode > 65535:
self.assertRaises(ValueError, scanstring, '\\ud834"', 0, None, True),
self.assertRaises(ValueError, scanstring, '\\ud834\\u"', 0, None, True),
self.assertRaises(ValueError, scanstring, '\\ud834\\x0123"', 0, None, True),
def test_issue3623(self):
self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
"xxx")
self.assertRaises(UnicodeDecodeError,
json.encoder.encode_basestring_ascii, b("xx\xff"))
def test_overflow(self):
# Python 2.5 does not have maxsize, Python 3 does not have maxint
maxsize = getattr(sys, 'maxsize', getattr(sys, 'maxint', None))
assert maxsize is not None
self.assertRaises(OverflowError, json.decoder.scanstring, "xxx",
maxsize + 1)

View File

@@ -1,42 +0,0 @@
import textwrap
from unittest import TestCase
import simplejson as json
class TestSeparators(TestCase):
def test_separators(self):
h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
{'nifty': 87}, {'field': 'yes', 'morefield': False} ]
expect = textwrap.dedent("""\
[
[
"blorpie"
] ,
[
"whoops"
] ,
[] ,
"d-shtaeou" ,
"d-nthiouh" ,
"i-vhbjkhnth" ,
{
"nifty" : 87
} ,
{
"field" : "yes" ,
"morefield" : false
}
]""")
d1 = json.dumps(h)
d2 = json.dumps(h, indent=' ', sort_keys=True, separators=(' ,', ' : '))
h1 = json.loads(d1)
h2 = json.loads(d2)
self.assertEqual(h1, h)
self.assertEqual(h2, h)
self.assertEqual(d2, expect)

View File

@@ -1,20 +0,0 @@
from unittest import TestCase
from simplejson import encoder, scanner
def has_speedups():
return encoder.c_make_encoder is not None
class TestDecode(TestCase):
def test_make_scanner(self):
if not has_speedups():
return
self.assertRaises(AttributeError, scanner.c_make_scanner, 1)
def test_make_encoder(self):
if not has_speedups():
return
self.assertRaises(TypeError, encoder.c_make_encoder,
None,
"\xCD\x7D\x3D\x4E\x12\x4C\xF9\x79\xD7\x52\xBA\x82\xF2\x27\x4A\x7D\xA0\xCA\x75",
None)

View File

@@ -1,51 +0,0 @@
import unittest
from simplejson.compat import StringIO
import simplejson as json
class TestTuples(unittest.TestCase):
def test_tuple_array_dumps(self):
t = (1, 2, 3)
expect = json.dumps(list(t))
# Default is True
self.assertEqual(expect, json.dumps(t))
self.assertEqual(expect, json.dumps(t, tuple_as_array=True))
self.assertRaises(TypeError, json.dumps, t, tuple_as_array=False)
# Ensure that the "default" does not get called
self.assertEqual(expect, json.dumps(t, default=repr))
self.assertEqual(expect, json.dumps(t, tuple_as_array=True,
default=repr))
# Ensure that the "default" gets called
self.assertEqual(
json.dumps(repr(t)),
json.dumps(t, tuple_as_array=False, default=repr))
def test_tuple_array_dump(self):
t = (1, 2, 3)
expect = json.dumps(list(t))
# Default is True
sio = StringIO()
json.dump(t, sio)
self.assertEqual(expect, sio.getvalue())
sio = StringIO()
json.dump(t, sio, tuple_as_array=True)
self.assertEqual(expect, sio.getvalue())
self.assertRaises(TypeError, json.dump, t, StringIO(),
tuple_as_array=False)
# Ensure that the "default" does not get called
sio = StringIO()
json.dump(t, sio, default=repr)
self.assertEqual(expect, sio.getvalue())
sio = StringIO()
json.dump(t, sio, tuple_as_array=True, default=repr)
self.assertEqual(expect, sio.getvalue())
# Ensure that the "default" gets called
sio = StringIO()
json.dump(t, sio, tuple_as_array=False, default=repr)
self.assertEqual(
json.dumps(repr(t)),
sio.getvalue())
class TestNamedTuple(unittest.TestCase):
def test_namedtuple_dump(self):
pass

View File

@@ -1,156 +0,0 @@
import sys
from unittest import TestCase
import simplejson as json
from simplejson.compat import unichr, text_type, b, u
class TestUnicode(TestCase):
def test_encoding1(self):
encoder = json.JSONEncoder(encoding='utf-8')
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
s = u.encode('utf-8')
ju = encoder.encode(u)
js = encoder.encode(s)
self.assertEqual(ju, js)
def test_encoding2(self):
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
s = u.encode('utf-8')
ju = json.dumps(u, encoding='utf-8')
js = json.dumps(s, encoding='utf-8')
self.assertEqual(ju, js)
def test_encoding3(self):
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
j = json.dumps(u)
self.assertEqual(j, '"\\u03b1\\u03a9"')
def test_encoding4(self):
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
j = json.dumps([u])
self.assertEqual(j, '["\\u03b1\\u03a9"]')
def test_encoding5(self):
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
j = json.dumps(u, ensure_ascii=False)
self.assertEqual(j, u'"' + u + u'"')
def test_encoding6(self):
u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
j = json.dumps([u], ensure_ascii=False)
self.assertEqual(j, u'["' + u + u'"]')
def test_big_unicode_encode(self):
u = u'\U0001d120'
self.assertEqual(json.dumps(u), '"\\ud834\\udd20"')
self.assertEqual(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
def test_big_unicode_decode(self):
u = u'z\U0001d120x'
self.assertEqual(json.loads('"' + u + '"'), u)
self.assertEqual(json.loads('"z\\ud834\\udd20x"'), u)
def test_unicode_decode(self):
for i in range(0, 0xd7ff):
u = unichr(i)
#s = '"\\u{0:04x}"'.format(i)
s = '"\\u%04x"' % (i,)
self.assertEqual(json.loads(s), u)
def test_object_pairs_hook_with_unicode(self):
s = u'{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
p = [(u"xkd", 1), (u"kcw", 2), (u"art", 3), (u"hxm", 4),
(u"qrt", 5), (u"pad", 6), (u"hoy", 7)]
self.assertEqual(json.loads(s), eval(s))
self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
od = json.loads(s, object_pairs_hook=json.OrderedDict)
self.assertEqual(od, json.OrderedDict(p))
self.assertEqual(type(od), json.OrderedDict)
# the object_pairs_hook takes priority over the object_hook
self.assertEqual(json.loads(s,
object_pairs_hook=json.OrderedDict,
object_hook=lambda x: None),
json.OrderedDict(p))
def test_default_encoding(self):
self.assertEqual(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
{'a': u'\xe9'})
def test_unicode_preservation(self):
self.assertEqual(type(json.loads(u'""')), text_type)
self.assertEqual(type(json.loads(u'"a"')), text_type)
self.assertEqual(type(json.loads(u'["a"]')[0]), text_type)
def test_ensure_ascii_false_returns_unicode(self):
# http://code.google.com/p/simplejson/issues/detail?id=48
self.assertEqual(type(json.dumps([], ensure_ascii=False)), text_type)
self.assertEqual(type(json.dumps(0, ensure_ascii=False)), text_type)
self.assertEqual(type(json.dumps({}, ensure_ascii=False)), text_type)
self.assertEqual(type(json.dumps("", ensure_ascii=False)), text_type)
def test_ensure_ascii_false_bytestring_encoding(self):
# http://code.google.com/p/simplejson/issues/detail?id=48
doc1 = {u'quux': b('Arr\xc3\xaat sur images')}
doc2 = {u'quux': u('Arr\xeat sur images')}
doc_ascii = '{"quux": "Arr\\u00eat sur images"}'
doc_unicode = u'{"quux": "Arr\xeat sur images"}'
self.assertEqual(json.dumps(doc1), doc_ascii)
self.assertEqual(json.dumps(doc2), doc_ascii)
self.assertEqual(json.dumps(doc1, ensure_ascii=False), doc_unicode)
self.assertEqual(json.dumps(doc2, ensure_ascii=False), doc_unicode)
def test_ensure_ascii_linebreak_encoding(self):
# http://timelessrepo.com/json-isnt-a-javascript-subset
s1 = u'\u2029\u2028'
s2 = s1.encode('utf8')
expect = '"\\u2029\\u2028"'
self.assertEqual(json.dumps(s1), expect)
self.assertEqual(json.dumps(s2), expect)
self.assertEqual(json.dumps(s1, ensure_ascii=False), expect)
self.assertEqual(json.dumps(s2, ensure_ascii=False), expect)
def test_invalid_escape_sequences(self):
# incomplete escape sequence
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u1')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u12')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u123')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u1234')
# invalid escape sequence
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u123x"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u12x4"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\u1x34"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ux234"')
if sys.maxunicode > 65535:
# unpaired low surrogate
self.assertRaises(json.JSONDecodeError, json.loads, '"\\udc00"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\udcff"')
# unpaired high surrogate
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800x"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800xx"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800xxxxxx"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u0"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u00"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u000"')
# invalid escape sequence for low surrogate
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u000x"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u00x0"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u0x00"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\ux000"')
# invalid value for low surrogate
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u0000"')
self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\ufc00"')
def test_ensure_ascii_still_works(self):
# in the ascii range, ensure that everything is the same
for c in map(unichr, range(0, 127)):
self.assertEqual(
json.dumps(c, ensure_ascii=False),
json.dumps(c))
snowman = u'\N{SNOWMAN}'
self.assertEqual(
json.dumps(c, ensure_ascii=False),
'"' + c + '"')

View File

@@ -1,42 +0,0 @@
r"""Command-line tool to validate and pretty-print JSON
Usage::
$ echo '{"json":"obj"}' | python -m simplejson.tool
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m simplejson.tool
Expecting property name: line 1 column 2 (char 2)
"""
from __future__ import with_statement
import sys
import simplejson as json
def main():
if len(sys.argv) == 1:
infile = sys.stdin
outfile = sys.stdout
elif len(sys.argv) == 2:
infile = open(sys.argv[1], 'r')
outfile = sys.stdout
elif len(sys.argv) == 3:
infile = open(sys.argv[1], 'r')
outfile = open(sys.argv[2], 'w')
else:
raise SystemExit(sys.argv[0] + " [infile [outfile]]")
with infile:
try:
obj = json.load(infile,
object_pairs_hook=json.OrderedDict,
use_decimal=True)
except ValueError:
raise SystemExit(sys.exc_info()[1])
with outfile:
json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
outfile.write('\n')
if __name__ == '__main__':
main()

View File

@@ -1,3 +1,24 @@
GAM 3.43
-Fix crash when authenticating GAM related to Short URLs
-minor fixes to Drive and Calendar commands
-catch unauthorized service account errors and offer nice instructions.
-execute bit for gam.py (Thanks daethnir)
GAM 3.42
-"gam <users> show driveactivity" displays Drive activity for user
-"gam report tokens" displays user OAuth activity report
-"gam mobile <id> action accountwipe" wipes account only on Android devices
-"gam <users> update labels" removes Inbox/ prefix from label names
-upgrades to oauth2client and googleapiclient libraries which GAM depends on
-"gam <users> show gmailprofile" shows Gmail mailbox details
-"gam license <sku>" commands to perform actions only on users with a given license
-bug fixes, lots of bug fixes
GAM 3.41
-fix Google servers not returning file size on audit export download
-soft fail on license change errors
-fix for gam info domain errors
GAM 3.4 "Oktoberfest"
-Support for creating and setting custom user schemas http://goo.gl/M9rQrI
-End user view of print users and user info commands.
@@ -89,7 +110,7 @@ GAM 2.992
GAM 2.991
-gam print commands now support a "todrive" argument. When specified, instead of displaying the CSV output locally (or piping it to a file), GAM will upload the CSV data to a Google Docs Spreadsheet owned by the admin you've authenticated as. The spreadsheet will be opened automatically or, if you've created nobrowser.txt, a URL will be shown.
-GAM should handle non-English input characters better. Commands like: "gam.py update user rpinaya lastname Piñaya" should work without issue on Windows.
-GAM should handle non-English input characters better. Commands like: "gam.py update user rpinaya lastname Piñaya" should work without issue on Windows.
-Improved errors in various places (less "explosions" more meaningful instructions.
-gam undelete user is fixed to always use the user's id rather than email address. If an email address is supplied, GAM converts it to a id before attempting to delete. If more than 1 deleted user exists with that address, GAM displays the options.