Compare commits

..

39 Commits
v4.12 ... v4.21

Author SHA1 Message Date
Jay Lee
c8c18497cc cleanup wixpdb files 2017-04-01 13:14:05 -04:00
Jay Lee
bdb0b3d6dc GAM 4.21 2017-04-01 12:53:11 -04:00
Ross Scroggs
7312c8f396 Standardize info cros/print cros/print crosactivity (#470) 2017-04-01 12:05:15 -04:00
Jay Lee
831272a137 Revert "Standardize info cros/print cros/print crosactivity (#468)" (#469)
This reverts commit d50231b888.
2017-04-01 10:30:29 -04:00
Ross Scroggs
d50231b888 Standardize info cros/print cros/print crosactivity (#468)
* Standardize info cros/print cros/print crosactivity

* Move _filterTimeRanges before 1st function that uses it.
2017-04-01 10:25:20 -04:00
Ross Scroggs
6160bb0953 Fix calendar add ACL (#465) 2017-03-31 13:27:49 -04:00
Ross Scroggs
1e013e6cd7 Fix add smime, isDefault can not be set on insert (#458)
* Update print drive settings to reflect change to Drive v3

* removeExpiration not passed to API

* Update documentation

* organizer is valid role on update drivefileacl

* Fix add smime, isDefault can not be set on insert
2017-03-31 13:02:34 -04:00
Jay Lee
96955d9305 Merge branch 'master' of https://github.com/jay0lee/GAM 2017-03-27 11:39:49 -04:00
Jay Lee
fdfa38a209 gam print crosactivity 2017-03-27 11:39:26 -04:00
Ross Scroggs
daa4b57af1 Drive v3 related changes (#456)
* Update print drive settings to reflect change to Drive v3

* removeExpiration not passed to API

* Update documentation

* organizer is valid role on update drivefileacl
2017-03-24 15:25:24 -04:00
Ross Scroggs
de3be6ba52 Update documentation (#454)
* Fix create project to handle Google change

* Handle newlines in app name

Don’t output name/value for
name == u'accounts:authorized_apps’, apps are listed later

* Cleanup

My typos: 614, 813
Pylint: 887, 3618/4184, 8657, 8751
Your typo: 6794

* Update documentation
2017-03-22 22:05:57 -04:00
Ross Scroggs
81c2e425ef Clean up (#453)
* Fix create project to handle Google change

* Handle newlines in app name

Don’t output name/value for
name == u'accounts:authorized_apps’, apps are listed later

* Cleanup

My typos: 614, 813
Pylint: 887, 3618/4184, 8657, 8751
Your typo: 6794
2017-03-22 20:39:44 -04:00
Jay Lee
4bf6b6fb96 What's new in 4.2 2017-03-22 16:56:37 -04:00
Ross Scroggs
4ed8497bd7 Minor fixes to showReport (#452)
* Fix create project to handle Google change

* Handle newlines in app name

Don’t output name/value for
name == u'accounts:authorized_apps’, apps are listed later
2017-03-22 15:45:20 -04:00
Ross Scroggs
738280bbe5 Fix create project to handle Google change (#451) 2017-03-22 14:33:27 -04:00
Jay Lee
ad9384aeac Soft errors on team drive functions 2017-03-22 13:59:38 -04:00
Jay Lee
67f5416858 Team Drive CSV and screen output 2017-03-22 13:34:28 -04:00
Jay Lee
51567ff5c4 fix report users, include msgValue items in report customer 2017-03-22 12:52:10 -04:00
Ross Scroggs
6f6a94c9b0 Handle bad data from Google in gam report customer (#450) 2017-03-22 11:12:22 -04:00
Jay Lee
08bd3ecc91 Get 4.2 ready 2017-03-19 14:06:17 -04:00
Jay Lee
6ebc0f4e81 Moar v3, Moar Team Drive 2017-03-19 14:05:46 -04:00
Jay Lee
bf39798263 Update Drive ACLs to v3 2017-03-18 21:07:51 -04:00
Jay Lee
92521acfa3 More v3 work 2017-03-18 20:36:45 -04:00
Jay Lee
f8d43a19c1 User counts for gam info domain 2017-03-18 13:32:48 -04:00
Jay Lee
842ddc2a26 httplib 0.10.3, api-client 1.6.2 2017-03-18 12:17:19 -04:00
Jay Lee
bafed078e5 Fix report name for todrive 2017-03-17 22:07:41 -04:00
Jay Lee
8999fb84de Switch report uploads to use SA, remove Drive scope 2017-03-17 20:05:06 -04:00
Jay Lee
3b162924c5 back to email scope, use SA for all Gmail API calls 2017-03-17 19:26:16 -04:00
Jay Lee
242c61205d Switch Calendar to use SA only 2017-03-17 12:57:31 -04:00
Jay Lee
139727dd33 Use service accounts for Calendar ACLs 2017-03-17 12:39:17 -04:00
Ross Scroggs
a52341e29e Improve API caching options, cache errors (#446)
* Improve API caching options, cache errors

* Clarify argument names

* All or nothing caching

I changed the argument names and implemented your proposal:
	nocache.txt: ignored
	allcache.txt present: all caching
        default: no caching
2017-03-16 11:23:42 -04:00
Jay Lee
c2358f60fb More Team Drive Work 2017-03-16 06:23:12 -04:00
Jay Lee
c8ea108be3 Use https://git.io/gam 2017-03-15 19:40:17 -04:00
Jay Lee
8fdd0abc53 scopes for drive3 2017-03-15 16:46:52 -04:00
Jay Lee
f396b2f476 Create Team Drives, show permissions and info 2017-03-15 16:07:40 -04:00
Jay Lee
6e9413eada drive3 API for Drive v3 API 2017-03-15 15:04:46 -04:00
Ross Scroggs
30a5467b82 Optimize License handling (#440)
* Handle batch file errors, clean up commit-batch messages

* Optimize License handling
2017-03-15 14:57:56 -04:00
Jay Lee
3ffa3ca5e5 If we can't get a safe filename, name the file after it's ID 2017-02-21 15:03:45 -05:00
Ross Scroggs
f0351b8bec Handle batch file errors, clean up commit-batch messages (#439) 2017-02-21 13:57:55 -05:00
12 changed files with 921 additions and 466 deletions

View File

@@ -1,6 +1,8 @@
This document describes the GAM command line syntax in modified BNF, see https://en.wikipedia.org/wiki/Backus-Naur_Form
Skip the History section and start reading at Introduction.
Items on the command line are space separated, when an actual space character is required, it will be indicated by <Space>.
If an item contains spaces, it should be surrounded by " or '.
If an item contains spaces, it should be surrounded by ".
[] optional item
() group items
@@ -13,7 +15,7 @@ Primitives
<Number> ::= <Digit>+
<Hex> ::= <Digit>|a|b|c|d|e|f|A|B|C|D|E|F
<Space> ::= an actual space character
<String> ::= a string of characters, surrounded by " or ' if it contains spaces
<String> ::= a string of characters, surrounded by " if it contains spaces
<TrueValues> ::= true|on|yes|enabled|1
<FalseValues>= false|off|no|disabled|0
<DataTransferService> ::= googledrive|gdrive|drive|"drive and docs"
@@ -82,7 +84,7 @@ Named items
<CrOSItem> ::= <CrOSID>|(query:<QueryCrOS>)
<DestEmailAddress> ::= <EmailAddress>
<DomainAlias> ::= <String>
<DriveFileACLRole> :: =commenter|editor|owner|reader|writer
<DriveFileACLRole> :: =commenter|editor|organizer|owner|reader|writer
<DriveFileID> ::= <String>
<DriveFileURL> :: = https://docs.google.com/a/<DomainName>/document/d/<DriveFileID>/<String>
<DriveFileItem> ::= <DriveFileID>|<DriveFileURL>
@@ -138,6 +140,7 @@ Named items
<Section> ::= <String>
<S/MIMEID> ::= <String>
<StudentItem> ::= <EmailAddress>|<UniqueID>|<String>
<TeamDriveID> ::= <String>
<Timezone> ::= <String>
<Title> ::= <String>
<URI> ::= <String>
@@ -162,7 +165,7 @@ Named items
teacherfolder|
teachergroupemail|
updatetime
<CourseFieldNameList> ::= '<CourseFieldName>(,<CourseFieldName>)*'
<CourseFieldNameList> ::= "<CourseFieldName>(,<CourseFieldName>)*"
<CrOSFieldName> ::=
activetimeranges|timeranges|
@@ -266,6 +269,7 @@ Named items
customreplyto|
defaultmessagedenynotificationtext|
description|
directmemberscount|
email|
id|
includeinglobaladdresslist|gal|
@@ -345,35 +349,35 @@ Named Lists
Lists can be in the following formats
Items, separated by commas, without spaces or commas in the items themselves: item(,item)*
Items, separated by spaces, without spaces or commas in the items themselves: "item( item)*"
Items, separated by commas, with spaces or commas in the items themselves: "'it em'(,'it em')*"
Items, separated by spaces, with spaces or commas in the items themselves: "'it em'( 'it em')*"
Items, separated by commas, with spaces or commas in the items themselves: "'it em'(,'it,em')*"
Items, separated by spaces, with spaces or commas in the items themselves: "'it em'( 'it,em')*"
<ACLList> ::== '<ACLScope>(,<ACLScope>)*'
<CalendarList> ::= '<CalendarItem>(,<CalendarItem>)*'
<CourseAliasList> ::= '<CourseAlias>(,<CourseAlias>)*'
<CourseIDList> ::= '<CourseID>(,<CourseID>)*'
<CrOSFieldNameList> ::= '<CrOSFieldName>(,<CrOSFieldName>)*'
<CrOSList> ::= '<CrOSID>(,<CrOSID>)*'
<DriveFileList> ::= '<DriveFileItem>(,<DriveFileItem>)*'
<EmailAddressList> ::= '<EmailAddress>(,<EmailAddress>)*'
<EventIDList> ::= '<EventID>(,<EventID>)*'
<FileFormatList> ::= '<FileFormat>(,<FileFormat)*'
<FilterIDList> ::= '<FilterID>(,<FilterID>)*'
<GroupFieldNameList> ::= '<GroupFieldName>(,<GroupFieldName>)*'
<GroupList> ::= '<GroupItem>(,<GroupItem>)*'
<GuardianStateList> ::= '<GuardianState>(,<GuardianState>)*'
<LabelNameList> ::= '<LabelName>(,<LabelName)*'
<MembersFieldNameList> ::= '<MembersFieldName>(,<MembersFieldName>)*'
<MobileList> ::= '<MobileId>(,<MobileId>)*'
<OrgUnitList> ::== '<OrgUnitPath>(,<OrgUnitPath>)*'
<PrinterIDList> ::= '<PrinterID>(,<PrinterID>)*'
<ProductIDList> ::= '(<ProductID>|SKUID>)(,<ProductID>|SKUID>)*'
<PrintJobIDList> ::= '<PrintJobID>(,<PrintJobID>)*'
<ResourceIDList> ::= '<ResourceID>(,<ResourceID>)*'
<SKUIDList> ='<SKUID>(,<SKUID>)*'
<SchemaNameList> ::= '<SchemaName>(,<SchemaName>)*'
<UserFieldNameList> ::= '<UserFieldName>(,<UserFieldName>)*'
<UserList> ::= '<UserItem>(,<UserItem>)*'
<ACLList> ::== "<ACLScope>(,<ACLScope>)*"
<CalendarList> ::= "<CalendarItem>(,<CalendarItem>)*"
<CourseAliasList> ::= "<CourseAlias>(,<CourseAlias>)*"
<CourseIDList> ::= "<CourseID>(,<CourseID>)*"
<CrOSFieldNameList> ::= "<CrOSFieldName>(,<CrOSFieldName>)*"
<CrOSList> ::= "<CrOSID>(,<CrOSID>)*"
<DriveFileList> ::= "<DriveFileItem>(,<DriveFileItem>)*"
<EmailAddressList> ::= "<EmailAddress>(,<EmailAddress>)*"
<EventIDList> ::= "<EventID>(,<EventID>)*"
<FileFormatList> ::= "<FileFormat>(,<FileFormat)*"
<FilterIDList> ::= "<FilterID>(,<FilterID>)*"
<GroupFieldNameList> ::= "<GroupFieldName>(,<GroupFieldName>)*"
<GroupList> ::= "<GroupItem>(,<GroupItem>)*"
<GuardianStateList> ::= "<GuardianState>(,<GuardianState>)*"
<LabelNameList> ::= "<LabelName>(,<LabelName)*"
<MembersFieldNameList> ::= "<MembersFieldName>(,<MembersFieldName>)*"
<MobileList> ::= "<MobileId>(,<MobileId>)*"
<OrgUnitList> ::== "<OrgUnitPath>(,<OrgUnitPath>)*"
<PrinterIDList> ::= "<PrinterID>(,<PrinterID>)*"
<ProductIDList> ::= "(<ProductID>|SKUID>)(,<ProductID>|SKUID>)*"
<PrintJobIDList> ::= "<PrintJobID>(,<PrintJobID>)*"
<ResourceIDList> ::= "<ResourceID>(,<ResourceID>)*"
<SKUIDList> ="<SKUID>(,<SKUID>)*"
<SchemaNameList> ::= "<SchemaName>(,<SchemaName>)*"
<UserFieldNameList> ::= "<UserFieldName>(,<UserFieldName>)*"
<UserList> ::= "<UserItem>(,<UserItem>)*"
Specify a collection of ChromeOS devices by directly specifying them
<CrOSTypeEntity> ::=
@@ -506,7 +510,7 @@ Item attributes
(relation|relations clear|(spouse|child|mother|father|parent|brother|sister|friend|relative|domestic_partner|manager|assistant|referred_by|partner|<String> <String>))|
(suspended <Boolean>)|
(website|websites clear|(home_page|blog|profile|work|home|other|ftp|reservations|app_install_page|<String> <URL> [notprimary|primary]))|
(<SchemaName>.<FieldName> [multivalued|multivalue|value [type work|home|other|(custom <String>)]] <String>)
(<SchemaName>.<FieldName> [multivalued|multivalue|value|multinonempty [type work|home|other|(custom <String>)]] <String>)
gam version [check] [simple]
gam help
@@ -518,7 +522,7 @@ You can make substitutions in <GAMArgumentList> with values from the CSV file.
An argument containing exactly ~xxx is replaced by the value of field xxx from the CSV file
An argument containing instances of ~~xxx~~ has xxx replaced by the value of field xxx from the CSV file
Example: gam csv Users.csv gam update user '~primaryEmail' address type work unstructured '~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~'
Example: gam csv Users.csv gam update user "~primaryEmail" address type work unstructured "~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~"
Each user (~primaryEmail, e.g. foo@bar.com) would have their work address updated
gam create project [<EmailAddress>]
@@ -541,9 +545,9 @@ gam update resoldcustomer <CustomerID> [customer_auth_token <String>]
[locality|city <String>] [region|state <String>] [postal|postal_code <String>] [country|country_code <String>]
gam info resoldcustomer <CustomerID>
gam create resoldsubscription <CustomerID>
[customer_auth_token <String>] [plan annual_monthly_pay|annual_yearly_pay|flexible|trial]
[deal <String>] [purchaseorderid <String>] [seats <NumberOfSeats> <MaximumNumberOfSeats>] [sku <SKUID>]
gam create resoldsubscription <CustomerID> (sku <SKUID>)
(plan annual_monthly_pay|annual_yearly_pay|flexible|trial) (seats <NumberOfSeats> <MaximumNumberOfSeats>)
[customer_auth_token <String>] [deal <String>] [purchaseorderid <String>]
gam update resoldsubscription <CustomerID> <SKUID>
activate|suspend|startpaidservice|
(renewal auto_renew_monthly_pay|auto_renew_yearly_pay|cancel|renew_current_users_monthly_pay|renew_current_users_yearly_pay|switch_to_pay_as_you_go)|
@@ -684,7 +688,7 @@ gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [nosch
gam print users [todrive] ([domain <DomainName>] [query <QueryUser>] [deleted_only|only_deleted])
[groups] [license|licenses|licence|licences] [emailpart|emailparts|username]
[orderby <UserOrderByFieldName> [ascending|descending]] [userview]
[basic|full|allfields | <UserFieldName>* | fields <UserFieldNameList>] [schemas|custom all|<SchemaNameList>]
[allfields|basic|full | ((<UserFieldName>* | fields <UserFieldNameList>) [schemas|custom all|<SchemaNameList>])]
gam <UserTypeEntity> print
Summary of printing:
@@ -780,9 +784,9 @@ gam <UserTypeEntity> delete|del emptydrivefolders
gam <UserTypeEntity> empty drivetrash
gam <UserTypeEntity> add drivefileacl <DriveFileID> anyone|(user <UserItem>)|(group <GroupItem>)|(domain <DomainName>)
(role <DriveFileACLRole>) [withlink] [sendemail] [emailmessage <String>]
(role <DriveFileACLRole>) [withlink|discoverable] [sendemail] [emailmessage <String>]
gam <UserTypeEntity> update drivefileacl <DriveFileID> <PermissionID>
(role <DriveFileACLRole>) [withlink] [transferownership <Boolean>]
(role <DriveFileACLRole>) [withlink|discoverable] [removeexpiration]
gam <UserTypeEntity> delete|del drivefileacl <DriveFileID> <PermissionID>
gam <UserTypeEntity> show drivefileacl <DriveFileID>
@@ -870,6 +874,12 @@ gam <UserTypeEntity> print smime [todrive] [primaryonly]
gam <UserTypeEntity> signature|sig <String>|(file <FileName> [charset <Charset>]) (replace <Tag> <String>)* [html] [name <String>] [replyto <EmailAddress>] [default] [treatasalias <Boolean>]
gam <UserTypeEntity> show signature|sig [format]
gam <UserTypeEntity> add teamdrive <Name>
gam <UserTypeEntity> update teamdrive <TeamDriveID> [name <Name>]
gam <UserTypeEntity> delete teamdrive <TeamDriveID>
gam <UserTypeEntity> show teamdrives
gam <UserTypeEntity> print teamdrives [todrive]
gam <UserTypeEntity> vacation <FalseValues>
gam <UserTypeEntity> vacation <TrueValues> subject <String> (message <String>)|(file <FileName> [charset <CharSet>]) (replace <Tag> <String>)* [html]
[contactsonly] [domainonly] [startdate <Date>] [enddate <Date>]

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__version__ = "1.6.1"
__version__ = "1.6.2"
# Set default logging handler to avoid "No handler found" warnings.
import logging

View File

@@ -14,8 +14,6 @@
"""Helpers for authentication using oauth2client or google-auth."""
import httplib2
try:
import google.auth
import google.auth.credentials
@@ -31,6 +29,8 @@ try:
except ImportError: # pragma: NO COVER
HAS_OAUTH2CLIENT = False
from googleapiclient.http import build_http
def default_credentials():
"""Returns Application Default Credentials."""
@@ -86,6 +86,7 @@ def authorized_http(credentials):
"""
if HAS_GOOGLE_AUTH and isinstance(
credentials, google.auth.credentials.Credentials):
return google_auth_httplib2.AuthorizedHttp(credentials)
return google_auth_httplib2.AuthorizedHttp(credentials,
http=build_http())
else:
return credentials.authorize(httplib2.Http())
return credentials.authorize(build_http())

View File

@@ -61,6 +61,7 @@ from googleapiclient.errors import MediaUploadSizeError
from googleapiclient.errors import UnacceptableMimeTypeError
from googleapiclient.errors import UnknownApiNameOrVersion
from googleapiclient.errors import UnknownFileType
from googleapiclient.http import build_http
from googleapiclient.http import BatchHttpRequest
from googleapiclient.http import HttpMock
from googleapiclient.http import HttpMockSequence
@@ -97,6 +98,7 @@ V2_DISCOVERY_URI = ('https://{api}.googleapis.com/$discovery/rest?'
'version={apiVersion}')
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}
BODY_PARAMETER_DEFAULT_VALUE = {
'description': 'The request body.',
@@ -115,6 +117,7 @@ MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE = {
'type': 'string',
'required': False,
}
_PAGE_TOKEN_NAMES = ('pageToken', 'nextPageToken')
# Parameters accepted by the stack, but not visible via discovery.
# TODO(dhermes): Remove 'userip' in 'v2'.
@@ -213,7 +216,10 @@ def build(serviceName,
'apiVersion': version
}
discovery_http = http if http is not None else httplib2.Http()
if http is None:
discovery_http = build_http()
else:
discovery_http = http
for discovery_url in (discoveryServiceUrl, V2_DISCOVERY_URI,):
requested_url = uritemplate.expand(discovery_url, params)
@@ -328,6 +334,10 @@ def build_from_document(
if http is not None and credentials is not None:
raise ValueError('Arguments http and credentials are mutually exclusive.')
if developerKey is not None and credentials is not None:
raise ValueError(
'Arguments developerKey and credentials are mutually exclusive.')
if isinstance(service, six.string_types):
service = json.loads(service)
@@ -350,8 +360,9 @@ def build_from_document(
scopes = list(
service.get('auth', {}).get('oauth2', {}).get('scopes', {}).keys())
# If so, then the we need to setup authentication.
if scopes:
# If so, then the we need to setup authentication if no developerKey is
# specified.
if scopes and not developerKey:
# If the user didn't pass in credentials, attempt to acquire application
# default credentials.
if credentials is None:
@@ -366,7 +377,7 @@ def build_from_document(
# If the service doesn't require scopes then there is no need for
# authentication.
else:
http = httplib2.Http()
http = build_http()
if model is None:
features = service.get('features', [])
@@ -718,7 +729,11 @@ def createMethod(methodName, methodDesc, rootDesc, schema):
for name in parameters.required_params:
if name not in kwargs:
raise TypeError('Missing required parameter "%s"' % name)
# temporary workaround for non-paging methods incorrectly requiring
# page token parameter (cf. drive.changes.watch vs. drive.changes.list)
if name not in _PAGE_TOKEN_NAMES or _findPageTokenName(
_methodProperties(methodDesc, schema, 'response')):
raise TypeError('Missing required parameter "%s"' % name)
for name, regex in six.iteritems(parameters.pattern_params):
if name in kwargs:
@@ -921,13 +936,20 @@ def createMethod(methodName, methodDesc, rootDesc, schema):
return (methodName, method)
def createNextMethod(methodName):
def createNextMethod(methodName,
pageTokenName='pageToken',
nextPageTokenName='nextPageToken',
isPageTokenParameter=True):
"""Creates any _next methods for attaching to a Resource.
The _next methods allow for easy iteration through list() responses.
Args:
methodName: string, name of the method to use.
pageTokenName: string, name of request page token field.
nextPageTokenName: string, name of response page token field.
isPageTokenParameter: Boolean, True if request page token is a query
parameter, False if request page token is a field of the request body.
"""
methodName = fix_method_name(methodName)
@@ -945,24 +967,24 @@ Returns:
# Retrieve nextPageToken from previous_response
# Use as pageToken in previous_request to create new request.
if 'nextPageToken' not in previous_response or not previous_response['nextPageToken']:
nextPageToken = previous_response.get(nextPageTokenName, None)
if not nextPageToken:
return None
request = copy.copy(previous_request)
pageToken = previous_response['nextPageToken']
parsed = list(urlparse(request.uri))
q = parse_qsl(parsed[4])
# Find and remove old 'pageToken' value from URI
newq = [(key, value) for (key, value) in q if key != 'pageToken']
newq.append(('pageToken', pageToken))
parsed[4] = urlencode(newq)
uri = urlunparse(parsed)
request.uri = uri
logger.info('URL being requested: %s %s' % (methodName,uri))
if isPageTokenParameter:
# Replace pageToken value in URI
request.uri = _add_query_parameter(
request.uri, pageTokenName, nextPageToken)
logger.info('Next page request URL: %s %s' % (methodName, request.uri))
else:
# Replace pageToken value in request body
model = self._model
body = model.deserialize(request.body)
body[pageTokenName] = nextPageToken
request.body = model.serialize(body)
logger.info('Next page request body: %s %s' % (methodName, body))
return request
@@ -1110,19 +1132,59 @@ class Resource(object):
method.__get__(self, self.__class__))
def _add_next_methods(self, resourceDesc, schema):
# Add _next() methods
# Look for response bodies in schema that contain nextPageToken, and methods
# that take a pageToken parameter.
if 'methods' in resourceDesc:
for methodName, methodDesc in six.iteritems(resourceDesc['methods']):
if 'response' in methodDesc:
responseSchema = methodDesc['response']
if '$ref' in responseSchema:
responseSchema = schema.get(responseSchema['$ref'])
hasNextPageToken = 'nextPageToken' in responseSchema.get('properties',
{})
hasPageToken = 'pageToken' in methodDesc.get('parameters', {})
if hasNextPageToken and hasPageToken:
fixedMethodName, method = createNextMethod(methodName + '_next')
self._set_dynamic_attr(fixedMethodName,
method.__get__(self, self.__class__))
# Add _next() methods if and only if one of the names 'pageToken' or
# 'nextPageToken' occurs among the fields of both the method's response
# type either the method's request (query parameters) or request body.
if 'methods' not in resourceDesc:
return
for methodName, methodDesc in six.iteritems(resourceDesc['methods']):
nextPageTokenName = _findPageTokenName(
_methodProperties(methodDesc, schema, 'response'))
if not nextPageTokenName:
continue
isPageTokenParameter = True
pageTokenName = _findPageTokenName(methodDesc.get('parameters', {}))
if not pageTokenName:
isPageTokenParameter = False
pageTokenName = _findPageTokenName(
_methodProperties(methodDesc, schema, 'request'))
if not pageTokenName:
continue
fixedMethodName, method = createNextMethod(
methodName + '_next', pageTokenName, nextPageTokenName,
isPageTokenParameter)
self._set_dynamic_attr(fixedMethodName,
method.__get__(self, self.__class__))
def _findPageTokenName(fields):
"""Search field names for one like a page token.
Args:
fields: container of string, names of fields.
Returns:
First name that is either 'pageToken' or 'nextPageToken' if one exists,
otherwise None.
"""
return next((tokenName for tokenName in _PAGE_TOKEN_NAMES
if tokenName in fields), None)
def _methodProperties(methodDesc, schema, name):
"""Get properties of a field in a method description.
Args:
methodDesc: object, fragment of deserialized discovery document that
describes the method.
schema: object, mapping of schema names to schema descriptions.
name: string, name of top-level field in method description.
Returns:
Object representing fragment of deserialized discovery document
corresponding to 'properties' field of object corresponding to named field
in method description, if it exists, otherwise empty dict.
"""
desc = methodDesc.get(name, {})
if '$ref' in desc:
desc = schema.get(desc['$ref'], {})
return desc.get('properties', {})

View File

@@ -80,6 +80,8 @@ MAX_URI_LENGTH = 2048
_TOO_MANY_REQUESTS = 429
DEFAULT_HTTP_TIMEOUT_SEC = 60
def _should_retry_response(resp_status, content):
"""Determines whether a response should be retried.
@@ -815,6 +817,7 @@ class HttpRequest(object):
if 'content-length' not in self.headers:
self.headers['content-length'] = str(self.body_size)
# If the request URI is too long then turn it into a POST request.
# Assume that a GET request never contains a request body.
if len(self.uri) > MAX_URI_LENGTH and self.method == 'GET':
self.method = 'POST'
self.headers['x-http-method-override'] = 'GET'
@@ -1732,3 +1735,21 @@ def tunnel_patch(http):
http.request = new_request
return http
def build_http():
"""Builds httplib2.Http object
Returns:
A httplib2.Http object, which is used to make http requests, and which has timeout set by default.
To override default timeout call
socket.setdefaulttimeout(timeout_in_sec)
before interacting with this method.
"""
if socket.getdefaulttimeout() is not None:
http_timeout = socket.getdefaulttimeout()
else:
http_timeout = DEFAULT_HTTP_TIMEOUT_SEC
return httplib2.Http(timeout=http_timeout)

View File

@@ -23,10 +23,10 @@ __all__ = ['init']
import argparse
import httplib2
import os
from googleapiclient import discovery
from googleapiclient.http import build_http
from oauth2client import client
from oauth2client import file
from oauth2client import tools
@@ -88,7 +88,7 @@ def init(argv, name, version, doc, filename, scope=None, parents=[], discovery_f
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = tools.run_flow(flow, storage, flags)
http = credentials.authorize(http = httplib2.Http())
http = credentials.authorize(http=build_http())
if discovery_filename is None:
# Construct a service object via the discovery service.

View File

@@ -161,13 +161,14 @@ class Schemas(object):
# Return with trailing comma and newline removed.
return self._prettyPrintSchema(schema, dent=1)[:-2]
def get(self, name):
def get(self, name, default=None):
"""Get deserialized JSON schema from the schema name.
Args:
name: string, Schema name.
default: object, return value if name not found.
"""
return self.schemas[name]
return self.schemas.get(name, default)
class _SchemaToStruct(object):

View File

@@ -23,7 +23,7 @@ __contributors__ = ["Thomas Broyer (t.broyer@ltgt.net)",
"Louis Nyffenegger",
"Alex Yu"]
__license__ = "MIT"
__version__ = "0.9.2"
__version__ = "0.10.3"
import re
import sys
@@ -65,42 +65,54 @@ except ImportError:
socks = None
# Build the appropriate socket wrapper for ssl
ssl = None
ssl_SSLError = None
ssl_CertificateError = None
try:
import ssl # python 2.6
ssl_SSLError = ssl.SSLError
def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation,
ca_certs, ssl_version, hostname):
if disable_validation:
cert_reqs = ssl.CERT_NONE
else:
cert_reqs = ssl.CERT_REQUIRED
if ssl_version is None:
ssl_version = ssl.PROTOCOL_SSLv23
import ssl # python 2.6
except ImportError:
pass
if ssl is not None:
ssl_SSLError = getattr(ssl, 'SSLError', None)
ssl_CertificateError = getattr(ssl, 'CertificateError', None)
if hasattr(ssl, 'SSLContext'): # Python 2.7.9
context = ssl.SSLContext(ssl_version)
context.verify_mode = cert_reqs
context.check_hostname = (cert_reqs != ssl.CERT_NONE)
if cert_file:
context.load_cert_chain(cert_file, key_file)
if ca_certs:
context.load_verify_locations(ca_certs)
return context.wrap_socket(sock, server_hostname=hostname)
else:
return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file,
cert_reqs=cert_reqs, ca_certs=ca_certs,
ssl_version=ssl_version)
except (AttributeError, ImportError):
ssl_SSLError = None
def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation,
ca_certs, ssl_version, hostname):
if not disable_validation:
raise CertificateValidationUnsupported(
"SSL certificate validation is not supported without "
"the ssl module installed. To avoid this error, install "
"the ssl module, or explicity disable validation.")
ssl_sock = socket.ssl(sock, key_file, cert_file)
return httplib.FakeSocket(sock, ssl_sock)
def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation,
ca_certs, ssl_version, hostname):
if disable_validation:
cert_reqs = ssl.CERT_NONE
else:
cert_reqs = ssl.CERT_REQUIRED
if ssl_version is None:
ssl_version = ssl.PROTOCOL_SSLv23
if hasattr(ssl, 'SSLContext'): # Python 2.7.9
context = ssl.SSLContext(ssl_version)
context.verify_mode = cert_reqs
context.check_hostname = (cert_reqs != ssl.CERT_NONE)
if cert_file:
context.load_cert_chain(cert_file, key_file)
if ca_certs:
context.load_verify_locations(ca_certs)
return context.wrap_socket(sock, server_hostname=hostname)
else:
return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file,
cert_reqs=cert_reqs, ca_certs=ca_certs,
ssl_version=ssl_version)
def _ssl_wrap_socket_unsupported(sock, key_file, cert_file, disable_validation,
ca_certs, ssl_version, hostname):
if not disable_validation:
raise CertificateValidationUnsupported(
"SSL certificate validation is not supported without "
"the ssl module installed. To avoid this error, install "
"the ssl module, or explicity disable validation.")
ssl_sock = socket.ssl(sock, key_file, cert_file)
return httplib.FakeSocket(sock, ssl_sock)
if ssl is None:
_ssl_wrap_socket = _ssl_wrap_socket_unsupported
if sys.version_info >= (2,3):
@@ -269,8 +281,8 @@ def safename(filename):
filename = re_slash.sub(",", filename)
# limit length of filename
if len(filename)>64:
filename=filename[:64]
if len(filename)>200:
filename=filename[:200]
return ",".join((filename, filemd5))
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')
@@ -1066,7 +1078,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
raise CertificateHostnameMismatch(
'Server presented certificate that does not match '
'host %s: %s' % (hostname, cert), hostname, cert)
except ssl_SSLError, e:
except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch), e:
if sock:
sock.close()
if self.sock:
@@ -1076,7 +1088,7 @@ class HTTPSConnectionWithTimeout(httplib.HTTPSConnection):
# to get at more detailed error information, in particular
# whether the error is due to certificate validation or
# something else (such as SSL protocol mismatch).
if e.errno == ssl.SSL_ERROR_SSL:
if getattr(e, 'errno', None) == ssl.SSL_ERROR_SSL:
raise SSLHandshakeError(e)
else:
raise
@@ -1155,18 +1167,11 @@ try:
server_software.startswith('Development/')):
raise NotRunningAppEngineEnvironment()
try:
from google.appengine.api import apiproxy_stub_map
if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None:
raise ImportError # Bail out; we're not actually running on App Engine.
from google.appengine.api.urlfetch import fetch
from google.appengine.api.urlfetch import InvalidURLError
except (ImportError, AttributeError):
from google3.apphosting.api import apiproxy_stub_map
if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None:
raise ImportError # Bail out; we're not actually running on App Engine.
from google3.apphosting.api.urlfetch import fetch
from google3.apphosting.api.urlfetch import InvalidURLError
from google.appengine.api import apiproxy_stub_map
if apiproxy_stub_map.apiproxy.GetStub('urlfetch') is None:
raise ImportError # Bail out; we're not actually running on App Engine.
from google.appengine.api.urlfetch import fetch
from google.appengine.api.urlfetch import InvalidURLError
# Update the connection classes to use the Googel App Engine specific ones.
SCHEME_TO_CONNECTION = {

View File

@@ -4,10 +4,10 @@ import platform
import re
gam_author = u'Jay Lee <jay0lee@gmail.com>'
gam_version = u'4.12'
gam_version = u'4.21'
gam_license = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = u'http://git.io/gam'
GAM_URL = u'https://git.io/gam'
GAM_INFO = u'GAM {0} - {1} / {2} / Python {3}.{4}.{5} {6} / {7} {8} /'.format(gam_version, GAM_URL,
gam_author,
sys.version_info[0], sys.version_info[1],
@@ -91,6 +91,7 @@ API_VER_MAPPING = {
u'datatransfer': u'datatransfer_v1',
u'directory': u'directory_v1',
u'drive': u'v2',
u'drive3': u'v3',
u'email-settings': u'v2',
u'gmail': u'v1',
u'groupssettings': u'v1',
@@ -107,6 +108,7 @@ API_SCOPE_MAPPING = {
u'https://www.googleapis.com/auth/drive'],
u'calendar': [u'https://www.googleapis.com/auth/calendar',],
u'drive': [u'https://www.googleapis.com/auth/drive',],
u'drive3': [u'https://www.googleapis.com/auth/drive',],
u'gmail': [u'https://mail.google.com/',
u'https://www.googleapis.com/auth/gmail.settings.basic',
u'https://www.googleapis.com/auth/gmail.settings.sharing',],
@@ -440,7 +442,9 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
u'supportenddate': [u'supportEndDate',],
u'tag': [u'annotatedAssetId',],
u'timeranges': [u'activeTimeRanges.activeTime', u'activeTimeRanges.date'],
u'times': [u'activeTimeRanges.activeTime', u'activeTimeRanges.date'],
u'user': [u'annotatedUser',],
u'users': [u'recentUsers.email', u'recentUsers.type'],
u'willautorenew': [u'willAutoRenew',],
}
@@ -469,6 +473,11 @@ CROS_SCALAR_PROPERTY_PRINT_ORDER = [
u'willAutoRenew',
]
CROS_RECENT_USERS_ARGUMENTS = [u'recentusers', u'users']
CROS_ACTIVE_TIME_RANGES_ARGUMENTS = [u'timeranges', u'activetimeranges', u'times']
CROS_START_ARGUMENTS = [u'start', u'startdate', u'oldestdate']
CROS_END_ARGUMENTS = [u'end', u'enddate']
#
# Global variables
#
@@ -501,6 +510,10 @@ GM_MAP_ROLE_ID_TO_NAME = u'ri2n'
GM_MAP_ROLE_NAME_TO_ID = u'rn2i'
# Dictionary mapping User ID to Name
GM_MAP_USER_ID_TO_NAME = u'ui2n'
# GAM cache directory. If no_cache is True, this variable will be set to None
GM_CACHE_DIR = u'gacd'
# Reset GAM cache directory after discovery
GM_CACHE_DISCOVERY_ONLY = u'gcdo'
#
GM_Globals = {
GM_SYSEXITRC: 0,
@@ -517,6 +530,8 @@ GM_Globals = {
GM_MAP_ROLE_ID_TO_NAME: None,
GM_MAP_ROLE_NAME_TO_ID: None,
GM_MAP_USER_ID_TO_NAME: None,
GM_CACHE_DIR: None,
GM_CACHE_DISCOVERY_ONLY: True,
}
#
# Global variables defined by environment variables/signal files
@@ -530,6 +545,8 @@ GC_AUTO_BATCH_MIN = u'auto_batch_min'
GC_BATCH_SIZE = u'batch_size'
# GAM cache directory. If no_cache is specified, this variable will be set to None
GC_CACHE_DIR = u'cache_dir'
# GAM cache discovery only. If no_cache is False, only API discovery calls will be cached
GC_CACHE_DISCOVERY_ONLY = u'cache_discovery_only'
# Character set of batch, csv, data files
GC_CHARSET = u'charset'
# Path to client_secrets.json
@@ -579,6 +596,7 @@ GC_Defaults = {
GC_AUTO_BATCH_MIN: 0,
GC_BATCH_SIZE: 50,
GC_CACHE_DIR: u'',
GC_CACHE_DISCOVERY_ONLY: True,
GC_CHARSET: DEFAULT_CHARSET,
GC_CLIENT_SECRETS_JSON: FN_CLIENT_SECRETS_JSON,
GC_CONFIG_DIR: u'',
@@ -588,16 +606,16 @@ GC_Defaults = {
GC_DOMAIN: u'',
GC_DRIVE_DIR: u'',
GC_DRIVE_MAX_RESULTS: 1000,
GC_NO_BROWSER: FALSE,
GC_NO_CACHE: FALSE,
GC_NO_UPDATE_CHECK: FALSE,
GC_NO_VERIFY_SSL: FALSE,
GC_NO_BROWSER: False,
GC_NO_CACHE: False,
GC_NO_UPDATE_CHECK: False,
GC_NO_VERIFY_SSL: False,
GC_NUM_THREADS: 25,
GC_OAUTH2_TXT: FN_OAUTH2_TXT,
GC_OAUTH2SERVICE_JSON: FN_OAUTH2SERVICE_JSON,
GC_SECTION: u'',
GC_SHOW_COUNTS_MIN: 0,
GC_SHOW_GETTINGS: TRUE,
GC_SHOW_GETTINGS: True,
GC_SITE_DIR: u'',
GC_USER_MAX_RESULTS: 500,
}
@@ -621,6 +639,7 @@ GC_VAR_INFO = {
GC_AUTO_BATCH_MIN: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)},
GC_BATCH_SIZE: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)},
GC_CACHE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY},
GC_CACHE_DISCOVERY_ONLY: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
GC_CHARSET: {GC_VAR_TYPE: GC_TYPE_STRING},
GC_CLIENT_SECRETS_JSON: {GC_VAR_TYPE: GC_TYPE_FILE},
GC_CONFIG_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY},

View File

@@ -1,3 +1,19 @@
GAM 4.21
- Drive v3 fixes/updates by Ross
- "gam print crosactivty" command outputs active users and times
- SMime and calendar ACL fixes by Ross
- standardized cros info/print functionality by Ross
GAM 4.2
- Create, Update, Delete and List Team Drives
- Start moving to Drive API v3
- Disable GAM cache by default to prevent errors (Ross)
- Use service accounts for all Calendar, Drive and Gmail operations to reduce scopes
- Fix "Unknown" errors due to a scope issue (may require "gam oauth revoke" and re-authentication)
- "gam info domain" shows basic user / license sums again
- "gam report customer" now shows more browser usage stats
- Fix project creation ToS error (Ross)
GAM 4.12
- Realtime 2SV user status in gam info user and gam print users. Thanks hajdbo!
- Reseller API support. Create and manage customers and subscriptions.

View File

@@ -5,8 +5,8 @@ rmdir /q /s dist
del /q /f gam-%1-windows.zip
del /q /f gam-%1-windows-x64.zip
del /q /f gam-%1-windows-x64.msi
del /q /f gam.wixobj
del /q /f gam.wixpdb
del /q /f *.wixobj
del /q /f *.wixpdb
c:\python27-32\scripts\pyinstaller --clean -F --distpath=gam windows-gam.spec
xcopy LICENSE gam\
@@ -25,4 +25,5 @@ xcopy GamCommands.txt gam-64\
set GAMVERSION=%1
"%ProgramFiles(x86)%\WiX Toolset v3.10\bin\candle.exe" -arch x64 gam.wxs
"%ProgramFiles(x86)%\WiX Toolset v3.10\bin\light.exe" -ext "%ProgramFiles(x86)%\WiX Toolset v3.10\bin\WixUIExtension.dll" gam.wixobj -o gam-%1-windows-x64.msi
"%ProgramFiles(x86)%\WiX Toolset v3.10\bin\light.exe" -ext "%ProgramFiles(x86)%\WiX Toolset v3.10\bin\WixUIExtension.dll" gam.wixobj -o gam-%1-windows-x64.msi
del /q /f gam-%1-windows-x64.wixpdb