Display course updates, create group updates
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-certs (push) Has been cancelled

This commit is contained in:
Ross Scroggs
2026-04-23 19:23:41 -07:00
parent 726f061b16
commit e4352129db
6 changed files with 223 additions and 83 deletions

View File

@@ -3162,6 +3162,7 @@ gam <UserTypeEntity> show contactdelegates [shownames] [csv]
name| name|
owneremail| owneremail|
ownerid| ownerid|
ownername|
room| room|
section| section|
students| students|
@@ -3275,16 +3276,16 @@ gam courses <CourseEntity> sync teachers [addonly|removeonly] [makefirstteachero
gam courses <CourseEntity> sync students [addonly|removeonly] <UserTypeEntity> gam courses <CourseEntity> sync students [addonly|removeonly] <UserTypeEntity>
gam info course <CourseID> [owneraccess] gam info course <CourseID> [owneraccess]
[owneremail] [alias|aliases] [show all|students|teachers] [countsonly] [owneremail] [ownername] [alias|aliases] [show all|students|teachers] [countsonly]
[fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
[formatjson] [formatjson]
gam info courses <CourseEntity> [owneraccess] gam info courses <CourseEntity> [owneraccess]
[owneremail] [alias|aliases] [show all|students|teachers] [countsonly] [owneremail] [ownername] [alias|aliases] [show all|students|teachers] [countsonly]
[fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
[formatjson] [formatjson]
gam print courses [todrive <ToDriveAttribute>*] gam print courses [todrive <ToDriveAttribute>*]
(course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]) (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
[owneremail] [owneremailmatchpattern <REMatchPattern>] [owneremail] [owneremailmatchpattern <REMatchPattern>] [ownername]
[alias|aliases|aliasesincolumns [delimiter <Character>]] [alias|aliases|aliasesincolumns [delimiter <Character>]]
[show all|students|teachers] [countsonly] [show all|students|teachers] [countsonly]
[timefilter creationtime|updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timefilter creationtime|updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
@@ -3398,14 +3399,16 @@ gam print course-announcements [todrive <ToDriveAttribute>*]
(course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>]) (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>])
(announcementids <CourseAnnouncementIDEntity>)|((announcementstates <CourseAnnouncementStateList>)* (announcementids <CourseAnnouncementIDEntity>)|((announcementstates <CourseAnnouncementStateList>)*
(orderby <CourseAnnouncementOrderByFieldName> [ascending|descending])*) (orderby <CourseAnnouncementOrderByFieldName> [ascending|descending])*)
[showcreatoremails|creatoremail] [fields <CourseAnnouncementFieldNameList>] [showcreatoremails|creatoremail] [showcreatornames|creatorname]
[fields <CourseAnnouncementFieldNameList>]
[timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
[countsonly] [formatjson [quotechar <Character>]] [countsonly] [formatjson [quotechar <Character>]]
gam print course-materials [todrive <ToDriveAttribute>*] gam print course-materials [todrive <ToDriveAttribute>*]
(course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>]) (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>])
(materialids <CourseMaterialIDEntity>)|((materialstates <CourseMaterialStateList>)* (materialids <CourseMaterialIDEntity>)|((materialstates <CourseMaterialStateList>)*
(orderby <CourseMaterialOrderByFieldName> [ascending|descending])*) (orderby <CourseMaterialOrderByFieldName> [ascending|descending])*)
[showcreatoremails|creatoremail] [showtopicnames] [fields <CourseMaterialFieldNameList>] [showcreatoremails|creatoremail] [showcreatornames|creatorname] [showtopicnames]
[fields <CourseMaterialFieldNameList>]
[timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
[oneitemperrow] [oneitemperrow]
[countsonly] [formatjson [quotechar <Character>]] [countsonly] [formatjson [quotechar <Character>]]
@@ -3426,7 +3429,8 @@ gam print course-works [todrive <ToDriveAttribute>*]
(course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>]) (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>])
(workids <CourseWorkIDEntity>)|((workstates <CourseWorkStateList>)* (workids <CourseWorkIDEntity>)|((workstates <CourseWorkStateList>)*
(orderby <CourseWorkOrderByFieldName> [ascending|descending])*) (orderby <CourseWorkOrderByFieldName> [ascending|descending])*)
[showcreatoremails|creatoremail] [showtopicnames] [fields <CourseWorkFieldNameList>] [showcreatoremails|creatoremail] [showcreatornames|creatorname] [showtopicnames]
[fields <CourseWorkFieldNameList>]
[showstudentsaslist [<Boolean>]] [delimiter <Character>] [showstudentsaslist [<Boolean>]] [delimiter <Character>]
[timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
[oneitemperrow] [oneitemperrow]
@@ -3884,6 +3888,8 @@ gam audit monitor list <EmailAddress>
gam create|add group <EmailAddress> gam create|add group <EmailAddress>
[copyfrom <GroupItem>] <GroupAttribute>* [copyfrom <GroupItem>] <GroupAttribute>*
[verifynotinvitable] [verifynotinvitable]
[recentdeleteretries <Integer>] [recentdeleteretrydelay <Integer>]
[verifycreationretries <Integer>] [verifycreationinitialdelay <Integer>] [verifycreationretrydelay <Integer>]
gam update group|groups <GroupEntity> [email <EmailAddress>] gam update group|groups <GroupEntity> [email <EmailAddress>]
[updateprimaryemail <RESearchPattern> <RESubstitution>] [updateprimaryemail <RESearchPattern> <RESubstitution>]
[copyfrom <GroupItem>] <GroupAttribute>* [copyfrom <GroupItem>] <GroupAttribute>*

View File

@@ -1,3 +1,31 @@
7.41.02
Added option `ownername` to `gam info|print courses` to have GAM display the course owners full name;
there is an extra API call per course to get the name.
Added option `creatorname` to `gam print course-announcements|course-materials|course-works` to have
GAM display the item creators full name; there is an extra API call per course to get the name.
After creating a group, it may be sometime, e.g. 30-45 seconds, before members can
successfully be added to the group even though the API reported that the group was created.
The following options can be used with `gam create group` to verify that the group is actually ready to be updated.
This will be most useful in scripts that are used to create and then populate groups.
```
verifycreationretries <Integer> - Verify group creation, defaults to 0, no verification performed, range 0-20
verifycreationinitialdelay <Integer> - Number of seconds to delay before first verification performed, defaults to 5, range 0-60
verifycreationretrydelay <Integer> - Number of seconds to delay between verificaton retries, defaults to 5, range 1-60
```
If you have a script that deletes a group and then immediately tries to create a new group with the same email address,
you may run into issues. There seems to be a 30-45 second window after the deletion in which a couple
of strange errors can occur on the creation: `Resource not found` and `Duplicate`.
The following options can be used with `gam create group` to handle these errors. This will be most useful
in scripts that are used to delete and then immediately recreate groups.
```
recentdeleteretries <Integer> - Handle group delete/create errors, defaults to 0, no errors handled, range 0-20
recentdeleteretrydelay <Integer> - Number of seconds to delay between retries, defaults to 5, range 1-60
```
7.41.01 7.41.01
Fixed bug in `gam print cigroups members managers owners countsonly totalcount internal external` that caused a trap. Fixed bug in `gam print cigroups members managers owners countsonly totalcount internal external` that caused a trap.

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.41.01' __version__ = '7.41.02'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
# pylint: disable=wrong-import-position # pylint: disable=wrong-import-position
@@ -4542,12 +4542,12 @@ class signjwtCredentials(google.oauth2.service_account.Credentials):
return token return token
def get_adc_request(): def get_adc_request():
# TODO: cache the result of is_on_gce() and check it here
# so we only check once on each GAM run.
request = google.auth.transport.requests.Request() request = google.auth.transport.requests.Request()
if gce_metadata.is_on_gce(request): if GM.Globals[GM.IS_ON_GCE]:
return request
if gce_metadata.is_on_gce(request):
GM.Globals[GM.IS_ON_GCE] = True
return request return request
else:
return transportCreateRequest() return transportCreateRequest()
class signjwtSignJwt(google.auth.crypt.Signer): class signjwtSignJwt(google.auth.crypt.Signer):
@@ -34018,9 +34018,20 @@ GROUP_ACCESS_TYPE_CHOICE_MAP = {
# gam create group <EmailAddress> [copyfrom <GroupItem>] <GroupAttribute>* # gam create group <EmailAddress> [copyfrom <GroupItem>] <GroupAttribute>*
# [verifynotinvitable] # [verifynotinvitable]
# [verifyduplicateretries <Integer>] [verifyduplicateretrydelay <Integer>]
# [verifycreationretries <Integer>] [verifycreationinitialdelay <Integer>] [verifycreationretrydelay <Integer>]
def doCreateGroup(ciGroupsAPI=False): def doCreateGroup(ciGroupsAPI=False):
def waitingForCreationToComplete(sleep_time):
writeStderr(Ind.Spaces()+Msg.WAITING_FOR_ITEM_CREATION_TO_COMPLETE_SLEEPING.format(Ent.Singular(Ent.GROUP), sleep_time))
time.sleep(sleep_time)
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
verifyNotInvitable = getBeforeUpdate = False verifyNotInvitable = getBeforeUpdate = False
recentDeleteRetries = 0
recentDeleteRetryDelay = 5
verifyCreationRetries = 0
verifyCreationInitialDelay = 5
verifyCreationRetryDelay = 5
groupEmail = getEmailAddress(noUid=True) groupEmail = getEmailAddress(noUid=True)
entityType = GROUP_CIGROUP_ENTITYTYPE_MAP[ciGroupsAPI] entityType = GROUP_CIGROUP_ENTITYTYPE_MAP[ciGroupsAPI]
if not ciGroupsAPI: if not ciGroupsAPI:
@@ -34060,6 +34071,16 @@ def doCreateGroup(ciGroupsAPI=False):
body['labels'][CIGROUP_LOCKED_LABEL] = '' body['labels'][CIGROUP_LOCKED_LABEL] = ''
elif myarg == 'verifynotinvitable': elif myarg == 'verifynotinvitable':
verifyNotInvitable = True verifyNotInvitable = True
elif myarg == 'recentdeleteretries':
recentDeleteRetries = getInteger(minVal=0, maxVal=20)
elif myarg == 'recentdeleteretrydelay':
recentDeleteRetryDelay = getInteger(minVal=1, maxVal=60)
elif myarg == 'verifycreationretries':
verifyCreationRetries = getInteger(minVal=0, maxVal=20)
elif myarg == 'verifycreationinitialdelay':
verifyCreationInitialDelay = getInteger(minVal=0, maxVal=60)
elif myarg == 'verifycreationretrydelay':
verifyCreationRetryDelay = getInteger(minVal=1, maxVal=60)
else: else:
getGroupAttrValue(myarg, gs_body) getGroupAttrValue(myarg, gs_body)
if verifyNotInvitable: if verifyNotInvitable:
@@ -34080,6 +34101,8 @@ def doCreateGroup(ciGroupsAPI=False):
return return
if not getBeforeUpdate: if not getBeforeUpdate:
settings = gs_body settings = gs_body
duplicateRetries = 0
while True:
try: try:
if not ciGroupsAPI: if not ciGroupsAPI:
callGAPI(cd.groups(), 'insert', callGAPI(cd.groups(), 'insert',
@@ -34090,28 +34113,77 @@ def doCreateGroup(ciGroupsAPI=False):
throwReasons=GAPI.CIGROUP_CREATE_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, throwReasons=GAPI.CIGROUP_CREATE_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS,
initialGroupConfig=initialGroupConfig, body=body, fields='') initialGroupConfig=initialGroupConfig, body=body, fields='')
if gs_body and not GroupIsAbuseOrPostmaster(groupEmail): if gs_body and not GroupIsAbuseOrPostmaster(groupEmail):
groupUniqueId = mapGroupEmailForSettings(groupEmail)
if getBeforeUpdate: if getBeforeUpdate:
settings = callGAPI(gs.groups(), 'get', settings = callGAPI(gs.groups(), 'get',
throwReasons=GAPI.GROUP_SETTINGS_THROW_REASONS, throwReasons=GAPI.GROUP_SETTINGS_THROW_REASONS,
retryReasons=GAPI.GROUP_SETTINGS_RETRY_REASONS+[GAPI.NOT_FOUND], retryReasons=GAPI.GROUP_SETTINGS_RETRY_REASONS+[GAPI.NOT_FOUND],
groupUniqueId=mapGroupEmailForSettings(groupEmail), fields='*') groupUniqueId=groupUniqueId, fields='*')
settings.update(gs_body) settings.update(gs_body)
callGAPI(gs.groups(), 'update', callGAPI(gs.groups(), 'update',
bailOnInvalidError='messageModerationLevel' in settings, bailOnInvalidError='messageModerationLevel' in settings,
throwReasons=GAPI.GROUP_SETTINGS_THROW_REASONS, throwReasons=GAPI.GROUP_SETTINGS_THROW_REASONS,
retryReasons=GAPI.GROUP_SETTINGS_RETRY_REASONS+[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT], retryReasons=GAPI.GROUP_SETTINGS_RETRY_REASONS+[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT],
groupUniqueId=mapGroupEmailForSettings(groupEmail), body=settings, fields='') groupUniqueId=groupUniqueId, body=settings, fields='')
entityActionPerformed([entityType, groupEmail]) entityActionPerformed([entityType, groupEmail])
break
except GAPI.resourceNotFound as e:
# If group we're trying to create was just deleted, Google gets confused; sleep and retry
duplicateRetries += 1
if ciGroupsAPI or duplicateRetries > recentDeleteRetries:
entityActionFailedWarning([entityType, groupEmail], str(e))
break
time.sleep(recentDeleteRetryDelay)
continue
except (GAPI.alreadyExists, GAPI.duplicate): except (GAPI.alreadyExists, GAPI.duplicate):
duplicateRetries += 1
if ciGroupsAPI or duplicateRetries > recentDeleteRetries:
duplicateAliasGroupUserWarning(cd, [entityType, groupEmail]) duplicateAliasGroupUserWarning(cd, [entityType, groupEmail])
except GAPI.notFound: break
entityActionFailedWarning([entityType, groupEmail], Msg.DOES_NOT_EXIST) time.sleep(recentDeleteRetryDelay)
continue
# except GAPI.notFound:
# entityActionFailedWarning([entityType, groupEmail], Msg.DOES_NOT_EXIST)
except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.backendError, except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.backendError,
GAPI.invalid, GAPI.invalidArgument, GAPI.invalidAttributeValue, GAPI.invalidInput, GAPI.invalidArgument, GAPI.failedPrecondition, GAPI.invalid, GAPI.invalidArgument, GAPI.invalidAttributeValue, GAPI.invalidInput, GAPI.invalidArgument, GAPI.failedPrecondition,
GAPI.badRequest, GAPI.permissionDenied, GAPI.systemError, GAPI.serviceLimit, GAPI.serviceNotAvailable, GAPI.authError) as e: GAPI.badRequest, GAPI.permissionDenied, GAPI.systemError, GAPI.serviceLimit, GAPI.serviceNotAvailable, GAPI.authError) as e:
entityActionFailedWarning([entityType, groupEmail], str(e)) entityActionFailedWarning([entityType, groupEmail], str(e))
break
except GAPI.required: except GAPI.required:
entityActionFailedWarning([entityType, groupEmail], Msg.INVALID_JSON_SETTING) entityActionFailedWarning([entityType, groupEmail], Msg.INVALID_JSON_SETTING)
break
if ciGroupsAPI or not verifyCreationRetries:
return
Act.Set(Act.VERIFYITEMEXISTS)
action = Act.Get()
performAction(Ent.GROUP, groupEmail)
Ind.Increment()
waitingForCreationToComplete(verifyCreationInitialDelay)
retries = 0
while True:
try:
callGAPI(cd.groups(), 'get',
throwReasons=GAPI.GROUP_GET_THROW_REASONS, retryReasons=GAPI.GROUP_GET_RETRY_REASONS,
groupKey=groupEmail, fields='name')
entityActionPerformed([Ent.GROUP, groupEmail])
break
except GAPI.groupNotFound:
retries += 1
kvList = [Act.PerformedName(action), False, 'Retry', f'{retries}/{verifyCreationRetries}']
printEntityKVList([Ent.GROUP, groupEmail], kvList)
if retries >= verifyCreationRetries:
entityActionFailedWarning([Ent.GROUP, groupEmail], Msg.RETRIES_EXHAUSTED.format(verifyCreationRetries))
break
waitingForCreationToComplete(verifyCreationRetryDelay)
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.backendError,
GAPI.invalid, GAPI.invalidArgument, GAPI.invalidMember, GAPI.invalidParameter, GAPI.invalidInput, GAPI.forbidden,
GAPI.badRequest, GAPI.permissionDenied, GAPI.systemError, GAPI.serviceLimit, GAPI.serviceNotAvailable, GAPI.authError) as e:
entityActionFailedWarning([Ent.GROUP, groupEmail], str(e))
break
except KeyboardInterrupt:
entityActionFailedWarning([Ent.GROUP, groupEmail], Msg.CHECK_INTERRUPTED)
break
Ind.Decrement()
# [addonly|removeonly] # [addonly|removeonly]
def getSyncOperation(): def getSyncOperation():
@@ -50287,6 +50359,7 @@ COURSE_FIELDS_CHOICE_MAP = {
'name': 'name', 'name': 'name',
'owneremail': 'ownerId', 'owneremail': 'ownerId',
'ownerid': 'ownerId', 'ownerid': 'ownerId',
'ownername': 'ownerId',
'room': 'room', 'room': 'room',
'section': 'section', 'section': 'section',
'teacherfolder': 'teacherFolder', 'teacherfolder': 'teacherFolder',
@@ -50309,6 +50382,7 @@ COURSE_PROPERTY_PRINT_ORDER = [
'alternateLink', 'alternateLink',
'ownerEmail', 'ownerEmail',
'ownerId', 'ownerId',
'ownerName',
'creationTime', 'creationTime',
'updateTime', 'updateTime',
'calendarId', 'calendarId',
@@ -50320,7 +50394,8 @@ COURSE_PROPERTY_PRINT_ORDER = [
] ]
def _initCourseShowProperties(fields=None): def _initCourseShowProperties(fields=None):
return {'aliases': False, 'aliasesInColumns': False, 'ownerEmail': False, 'ownerEmailMatchPattern': None, 'members': 'none', 'countsOnly': False, return {'aliases': False, 'aliasesInColumns': False, 'ownerEmail': False, 'ownerEmailMatchPattern': None,
'ownerName': False, 'members': 'none', 'countsOnly': False,
'fields': fields if fields is not None else [], 'skips': []} 'fields': fields if fields is not None else [], 'skips': []}
def _getCourseShowProperties(myarg, courseShowProperties): def _getCourseShowProperties(myarg, courseShowProperties):
@@ -50335,6 +50410,8 @@ def _getCourseShowProperties(myarg, courseShowProperties):
elif myarg == 'owneremailmatchpattern': elif myarg == 'owneremailmatchpattern':
courseShowProperties['ownerEmail'] = True courseShowProperties['ownerEmail'] = True
courseShowProperties['ownerEmailMatchPattern'] = getREPattern(re.IGNORECASE) courseShowProperties['ownerEmailMatchPattern'] = getREPattern(re.IGNORECASE)
elif myarg == 'ownername':
courseShowProperties['ownerName'] = True
elif myarg == 'show': elif myarg == 'show':
courseShowProperties['members'] = getChoice(COURSE_MEMBER_ARGUMENTS) courseShowProperties['members'] = getChoice(COURSE_MEMBER_ARGUMENTS)
elif myarg == 'countsonly': elif myarg == 'countsonly':
@@ -50350,6 +50427,9 @@ def _getCourseShowProperties(myarg, courseShowProperties):
elif field == 'owneremail': elif field == 'owneremail':
courseShowProperties['ownerEmail'] = True courseShowProperties['ownerEmail'] = True
courseShowProperties['fields'].append(COURSE_FIELDS_CHOICE_MAP[field]) courseShowProperties['fields'].append(COURSE_FIELDS_CHOICE_MAP[field])
elif field == 'ownername':
courseShowProperties['ownerName'] = True
courseShowProperties['fields'].append(COURSE_FIELDS_CHOICE_MAP[field])
elif field == 'teachers': elif field == 'teachers':
if courseShowProperties['members'] == 'none': if courseShowProperties['members'] == 'none':
courseShowProperties['members'] = field courseShowProperties['members'] = field
@@ -50391,27 +50471,26 @@ def _setCourseFields(courseShowProperties, pagesMode, getOwnerId=False):
if not courseShowProperties['fields']: if not courseShowProperties['fields']:
return None return None
courseShowProperties['fields'].append('id') courseShowProperties['fields'].append('id')
if courseShowProperties['ownerEmail'] or getOwnerId: if courseShowProperties['ownerEmail'] or courseShowProperties['ownerName'] or getOwnerId:
courseShowProperties['fields'].append('ownerId') courseShowProperties['fields'].append('ownerId')
if not pagesMode: if not pagesMode:
return ','.join(set(courseShowProperties['fields'])) return ','.join(set(courseShowProperties['fields']))
return f'nextPageToken,courses({",".join(set(courseShowProperties["fields"]))})' return f'nextPageToken,courses({",".join(set(courseShowProperties["fields"]))})'
def _convertCourseUserIdToEmail(croom, userId, emails, entityValueList, i, count): def _convertCourseUserIdToEmailName(croom, userId, emails, entityValueList, i, count):
userEmail = emails.get(userId) if userId not in emails:
if userEmail is None:
try: try:
userEmail = callGAPI(croom.userProfiles(), 'get', result = callGAPI(croom.userProfiles(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.BAD_REQUEST, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
userId=userId, fields='emailAddress').get('emailAddress') userId=userId, fields='emailAddress,name(fullName)')
except (GAPI.notFound, GAPI.permissionDenied, GAPI.badRequest, GAPI.forbidden, GAPI.serviceNotAvailable): except (GAPI.notFound, GAPI.permissionDenied, GAPI.badRequest, GAPI.forbidden, GAPI.serviceNotAvailable):
pass result = {}
if userEmail is None: if not result:
entityDoesNotHaveItemWarning(entityValueList, i, count) entityDoesNotHaveItemWarning(entityValueList, i, count)
userEmail = 'Unknown user' emails[userId] = (result.get('emailAddress', 'Unknown user'),
emails[userId] = userEmail result.get('name', {}).get('fullName', 'Unknown user'))
return userEmail return emails[userId]
def _getCourseOwnerSA(croom, course, useOwnerAccess): def _getCourseOwnerSA(croom, course, useOwnerAccess):
if not useOwnerAccess: if not useOwnerAccess:
@@ -50533,9 +50612,13 @@ def _doInfoCourses(courseIdList):
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
id=courseId, fields=fields) id=courseId, fields=fields)
if courseShowProperties['ownerEmail']: if courseShowProperties['ownerEmail'] or courseShowProperties['ownerName']:
course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails, ownerEmail, ownerName = _convertCourseUserIdToEmailName(croom, course['ownerId'], ownerEmails,
[Ent.COURSE, course['id'], Ent.OWNER_ID, course['ownerId']], i, count) [Ent.COURSE, course['id'], Ent.OWNER_ID, course['ownerId']], i, count)
if courseShowProperties['ownerEmail']:
course['ownerEmail'] = ownerEmail
if courseShowProperties['ownerName']:
course['ownerName'] = ownerName
aliases, teachers, students = _getCourseAliasesMembers(croom, courseInfo['croom'], courseId, courseShowProperties, teachersFields, studentsFields) aliases, teachers, students = _getCourseAliasesMembers(croom, courseInfo['croom'], courseId, courseShowProperties, teachersFields, studentsFields)
if FJQC.formatJSON: if FJQC.formatJSON:
if courseShowProperties['aliases']: if courseShowProperties['aliases']:
@@ -50599,14 +50682,14 @@ def _doInfoCourses(courseIdList):
ClientAPIAccessDeniedExit(str(e)) ClientAPIAccessDeniedExit(str(e))
# gam info courses <CourseEntity> [owneraccess] # gam info courses <CourseEntity> [owneraccess]
# [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly] # [owneremail] [ownername] [alias|aliases] [show none|all|students|teachers] [countsonly]
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] # [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
# [formatjson] # [formatjson]
def doInfoCourses(): def doInfoCourses():
_doInfoCourses(getEntityList(Cmd.OB_COURSE_ENTITY, shlexSplit=True)) _doInfoCourses(getEntityList(Cmd.OB_COURSE_ENTITY, shlexSplit=True))
# gam info course <CourseID> [owneraccess] # gam info course <CourseID> [owneraccess]
# [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly] # [owneremail] [ownername] [alias|aliases] [show none|all|students|teachers] [countsonly]
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] # [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
# [formatjson] # [formatjson]
def doInfoCourse(): def doInfoCourse():
@@ -50719,7 +50802,7 @@ def _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, getO
return coursesInfo return coursesInfo
# gam print courses [todrive <ToDriveAttribute>*] (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]) # gam print courses [todrive <ToDriveAttribute>*] (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
# [owneremail] [owneremailmatchpattern <REMatchPattern>] # [owneremail] [owneremailmatchpattern <REMatchPattern>] [ownername]
# [alias|aliases|aliasesincolumns [delimiter <Character>]] # [alias|aliases|aliasesincolumns [delimiter <Character>]]
# [show none|all|students|teachers] [countsonly] # [show none|all|students|teachers] [countsonly]
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] # [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
@@ -50838,11 +50921,15 @@ def doPrintCourses():
ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess) ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
if not ocroom: if not ocroom:
continue continue
if courseShowProperties['ownerEmail']: if courseShowProperties['ownerEmail'] or courseShowProperties['ownerName']:
course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails, ownerEmail, ownerName = _convertCourseUserIdToEmailName(croom, course['ownerId'], ownerEmails,
[Ent.COURSE, courseId, Ent.OWNER_ID, course['ownerId']], i, count) [Ent.COURSE, courseId, Ent.OWNER_ID, course['ownerId']], i, count)
if courseShowProperties['ownerEmailMatchPattern'] and not courseShowProperties['ownerEmailMatchPattern'].match(course['ownerEmail']): if courseShowProperties['ownerEmail']:
course['ownerEmail'] = ownerEmail
if courseShowProperties['ownerEmailMatchPattern'] and not courseShowProperties['ownerEmailMatchPattern'].match(ownerEmail):
continue continue
if courseShowProperties['ownerName']:
course['ownerName'] = ownerName
if showItemCountOnly: if showItemCountOnly:
itemCount += 1 itemCount += 1
continue continue
@@ -50944,10 +51031,14 @@ def doPrintCourseAnnouncements():
def _printCourseAnnouncement(course, courseAnnouncement, i, count): def _printCourseAnnouncement(course, courseAnnouncement, i, count):
if applyCourseItemFilter and not _courseItemPassesFilter(courseAnnouncement, courseItemFilter): if applyCourseItemFilter and not _courseItemPassesFilter(courseAnnouncement, courseItemFilter):
return return
if showCreatorEmail: if showCreatorEmail or showCreatorName:
courseAnnouncement['creatorUserEmail'] = _convertCourseUserIdToEmail(croom, courseAnnouncement['creatorUserId'], creatorEmails, creatorUserEmail, creatorUserName = _convertCourseUserIdToEmailName(croom, courseAnnouncement['creatorUserId'], creatorEmails,
[Ent.COURSE, course['id'], Ent.COURSE_ANNOUNCEMENT_ID, courseAnnouncement['id'], [Ent.COURSE, course['id'], Ent.COURSE_ANNOUNCEMENT_ID, courseAnnouncement['id'],
Ent.CREATOR_ID, courseAnnouncement['creatorUserId']], i, count) Ent.CREATOR_ID, courseAnnouncement['creatorUserId']], i, count)
if showCreatorEmail:
courseAnnouncement['creatorUserEmail'] = creatorUserEmail
if showCreatorName:
courseAnnouncement['creatorUserName'] = creatorUserName
row = flattenJSON(courseAnnouncement, flattened={'courseId': course['id'], 'courseName': course['name']}, timeObjects=COURSE_ANNOUNCEMENTS_TIME_OBJECTS) row = flattenJSON(courseAnnouncement, flattened={'courseId': course['id'], 'courseName': course['name']}, timeObjects=COURSE_ANNOUNCEMENTS_TIME_OBJECTS)
if not FJQC.formatJSON: if not FJQC.formatJSON:
csvPF.WriteRowTitles(row) csvPF.WriteRowTitles(row)
@@ -50967,7 +51058,7 @@ def doPrintCourseAnnouncements():
courseAnnouncementStates = [] courseAnnouncementStates = []
OBY = OrderBy(COURSE_ANNOUNCEMENTS_ORDERBY_CHOICE_MAP) OBY = OrderBy(COURSE_ANNOUNCEMENTS_ORDERBY_CHOICE_MAP)
creatorEmails = {} creatorEmails = {}
countsOnly = showCreatorEmail = False countsOnly = showCreatorEmail = showCreatorName = False
items = 'courseAnnouncements' items = 'courseAnnouncements'
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
@@ -50985,6 +51076,8 @@ def doPrintCourseAnnouncements():
OBY.GetChoice() OBY.GetChoice()
elif myarg in {'showcreatoremails', 'creatoremail'}: elif myarg in {'showcreatoremails', 'creatoremail'}:
showCreatorEmail = True showCreatorEmail = True
elif myarg in {'showcreatornames', 'creatorname'}:
showCreatorName = True
elif getFieldsList(myarg, COURSE_ANNOUNCEMENTS_FIELDS_CHOICE_MAP, fieldsList, initialField='id'): elif getFieldsList(myarg, COURSE_ANNOUNCEMENTS_FIELDS_CHOICE_MAP, fieldsList, initialField='id'):
pass pass
elif myarg == 'countsonly': elif myarg == 'countsonly':
@@ -51242,10 +51335,14 @@ def doPrintCourseWM(entityIDType, entityStateType):
def _printCourseWM(course, courseWM, i, count): def _printCourseWM(course, courseWM, i, count):
if applyCourseItemFilter and not _courseItemPassesFilter(courseWM, courseItemFilter): if applyCourseItemFilter and not _courseItemPassesFilter(courseWM, courseItemFilter):
return return
if showCreatorEmail: if showCreatorEmail or showCreatorName:
courseWM['creatorUserEmail'] = _convertCourseUserIdToEmail(croom, courseWM['creatorUserId'], creatorEmails, creatorUserEmail, creatorUserName = _convertCourseUserIdToEmailName(croom, courseWM['creatorUserId'], creatorEmails,
[Ent.COURSE, course['id'], entityIDType, courseWM['id'], [Ent.COURSE, course['id'], entityIDType, courseWM['id'],
Ent.CREATOR_ID, courseWM['creatorUserId']], i, count) Ent.CREATOR_ID, courseWM['creatorUserId']], i, count)
if showCreatorEmail:
courseWM['creatorUserEmail'] = creatorUserEmail
if showCreatorName:
courseWM['creatorUserName'] = creatorUserName
if showTopicNames: if showTopicNames:
topicId = courseWM.get('topicId') topicId = courseWM.get('topicId')
if topicId: if topicId:
@@ -51294,7 +51391,7 @@ def doPrintCourseWM(entityIDType, entityStateType):
courseShowProperties = _initCourseShowProperties(['name']) courseShowProperties = _initCourseShowProperties(['name'])
OBY = OrderBy(OrderbyChoiceMap) OBY = OrderBy(OrderbyChoiceMap)
creatorEmails = {} creatorEmails = {}
oneItemPerRow = showCreatorEmail = showTopicNames = False oneItemPerRow = showCreatorEmail = showCreatorName = showTopicNames = False
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER] delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
countsOnly = showStudentsAsList = False countsOnly = showStudentsAsList = False
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
@@ -51316,6 +51413,8 @@ def doPrintCourseWM(entityIDType, entityStateType):
csvPF.RemoveIndexedTitles('materials') csvPF.RemoveIndexedTitles('materials')
elif myarg in {'showcreatoremails', 'creatoremail'}: elif myarg in {'showcreatoremails', 'creatoremail'}:
showCreatorEmail = True showCreatorEmail = True
elif myarg in {'showcreatornames', 'creatorname'}:
showCreatorName = True
elif myarg == 'showtopicnames': elif myarg == 'showtopicnames':
showTopicNames = True showTopicNames = True
elif getFieldsList(myarg, FieldsChoiceMap, fieldsList, initialField='id'): elif getFieldsList(myarg, FieldsChoiceMap, fieldsList, initialField='id'):
@@ -51329,7 +51428,7 @@ def doPrintCourseWM(entityIDType, entityStateType):
csvPF.AddTitles(items) csvPF.AddTitles(items)
else: else:
FJQC.GetFormatJSONQuoteChar(myarg, True) FJQC.GetFormatJSONQuoteChar(myarg, True)
if showCreatorEmail and fieldsList: if (showCreatorEmail or showCreatorName) and fieldsList:
fieldsList.append('creatorUserId') fieldsList.append('creatorUserId')
if showTopicNames and fieldsList: if showTopicNames and fieldsList:
fieldsList.append('topicId') fieldsList.append('topicId')
@@ -51394,7 +51493,8 @@ def doPrintCourseWM(entityIDType, entityStateType):
# (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>]) # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>])
# (materialids <CourseMaterialIDEntity>)|((materialstates <CourseMaterialStateList>)* # (materialids <CourseMaterialIDEntity>)|((materialstates <CourseMaterialStateList>)*
# (orderby <CourseMaterialsOrderByFieldName> [ascending|descending])*) # (orderby <CourseMaterialsOrderByFieldName> [ascending|descending])*)
# [showcreatoremails|creatoremail] [showtopicnames] [fields <CourseMaterialFieldNameList>] # [showcreatoremails|creatoremail] [showcreatornames|creatorname] [showtopicnames]
# [fields <CourseMaterialFieldNameList>]
# [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] # [timefilter creationtime|updatetime|scheduledtime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
# [oneitemperrow] # [oneitemperrow]
# [countsonly|(formatjson [quotechar <Character>])] # [countsonly|(formatjson [quotechar <Character>])]
@@ -51405,7 +51505,8 @@ def doPrintCourseMaterials():
# (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>]) # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] states <CourseStateList>])
# (workids <CourseWorkIDEntity>)|((workstates <CourseWorkStateList>)* # (workids <CourseWorkIDEntity>)|((workstates <CourseWorkStateList>)*
# (orderby <CourseWorkOrderByFieldName> [ascending|descending])*) # (orderby <CourseWorkOrderByFieldName> [ascending|descending])*)
# [showcreatoremails|creatoremail] [showtopicnames] [fields <CourseWorkFieldNameList>] # [showcreatoremails|creatoremail] [showcreatornames|creatorname] [showtopicnames]
# [fields <CourseWorkFieldNameList>]
# [showstudentsaslist [<Boolean>]] [delimiter <Character>] # [showstudentsaslist [<Boolean>]] [delimiter <Character>]
# [timefilter creationtime|updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] # [timefilter creationtime|updatetime] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>]
# [oneitemperrow] # [oneitemperrow]

View File

@@ -134,6 +134,7 @@ class GamAction():
UNZIP = 'unzi' UNZIP = 'unzi'
USE = 'use ' USE = 'use '
VERIFY = 'vrfy' VERIFY = 'vrfy'
VERIFYITEMEXISTS = 'vexi'
WAITFORMAILBOX = 'wamb' WAITFORMAILBOX = 'wamb'
WATCH = 'watc' WATCH = 'watc'
WIPE = 'wipe' WIPE = 'wipe'
@@ -253,6 +254,7 @@ class GamAction():
UPLOAD: ['Uploaded', 'Upload'], UPLOAD: ['Uploaded', 'Upload'],
USE: ['Used', 'Use'], USE: ['Used', 'Use'],
VERIFY: ['Verified', 'Verify'], VERIFY: ['Verified', 'Verify'],
VERIFYITEMEXISTS: ['Verified Item Exists', 'Verify Item Exists'],
WAITFORMAILBOX: ['Mailbox is Setup', 'Check Mailbox is Setup'], WAITFORMAILBOX: ['Mailbox is Setup', 'Check Mailbox is Setup'],
WATCH: ['Watched', 'Watch'], WATCH: ['Watched', 'Watch'],
WIPE: ['Wiped', 'Wipe'], WIPE: ['Wiped', 'Wipe'],

View File

@@ -274,7 +274,7 @@ GMAIL_LIST_THROW_REASONS = [FAILED_PRECONDITION, PERMISSION_DENIED, INVALID, INV
GMAIL_SMIME_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST, INVALID_ARGUMENT, FORBIDDEN, NOT_FOUND, PERMISSION_DENIED] GMAIL_SMIME_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST, INVALID_ARGUMENT, FORBIDDEN, NOT_FOUND, PERMISSION_DENIED]
GROUP_GET_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR] GROUP_GET_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR]
GROUP_GET_RETRY_REASONS = [INVALID, SYSTEM_ERROR, SERVICE_NOT_AVAILABLE] GROUP_GET_RETRY_REASONS = [INVALID, SYSTEM_ERROR, SERVICE_NOT_AVAILABLE]
GROUP_CREATE_THROW_REASONS = [DUPLICATE, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_INPUT] GROUP_CREATE_THROW_REASONS = [DUPLICATE, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_INPUT, RESOURCE_NOT_FOUND]
GROUP_UPDATE_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_INPUT] GROUP_UPDATE_THROW_REASONS = [GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_INPUT]
GROUP_SETTINGS_THROW_REASONS = [NOT_FOUND, GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, SYSTEM_ERROR, PERMISSION_DENIED, GROUP_SETTINGS_THROW_REASONS = [NOT_FOUND, GROUP_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, SYSTEM_ERROR, PERMISSION_DENIED,
INVALID, INVALID_ARGUMENT, INVALID_PARAMETER, INVALID_ATTRIBUTE_VALUE, INVALID_INPUT, INVALID, INVALID_ARGUMENT, INVALID_PARAMETER, INVALID_ATTRIBUTE_VALUE, INVALID_INPUT,

View File

@@ -131,6 +131,8 @@ GAM_PATH = 'gpth'
GAM_TYPE = 'gtyp' GAM_TYPE = 'gtyp'
# Shared Service Account HTTP Object # Shared Service Account HTTP Object
HTTP_OBJECT = 'http' HTTP_OBJECT = 'http'
# Are we on Global Compute Engine
IS_ON_GCE = 'ogce'
# Length of last Got message # Length of last Got message
LAST_GOT_MSG_LEN = 'lgml' LAST_GOT_MSG_LEN = 'lgml'
# License SKUs # License SKUs
@@ -285,6 +287,7 @@ Globals = {
GAM_PATH: '.', GAM_PATH: '.',
GAM_TYPE: '', GAM_TYPE: '',
HTTP_OBJECT: None, HTTP_OBJECT: None,
IS_ON_GCE: False,
LAST_GOT_MSG_LEN: 0, LAST_GOT_MSG_LEN: 0,
LICENSE_SKUS: [], LICENSE_SKUS: [],
MAKE_BUILDING_ID_NAME_MAP: True, MAKE_BUILDING_ID_NAME_MAP: True,