gam7 7.19.3__py3-none-any.whl → 7.20.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of gam7 might be problematic. Click here for more details.

gam/__init__.py CHANGED
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
25
25
  """
26
26
 
27
27
  __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
28
- __version__ = '7.19.03'
28
+ __version__ = '7.20.01'
29
29
  __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
30
30
 
31
31
  #pylint: disable=wrong-import-position
@@ -4731,6 +4731,7 @@ def clearServiceCache(service):
4731
4731
  return False
4732
4732
 
4733
4733
  DISCOVERY_URIS = [googleapiclient.discovery.V1_DISCOVERY_URI, googleapiclient.discovery.V2_DISCOVERY_URI]
4734
+ CLASSROOM_DEVELOPER_PREVIEW_DISCOVERY_URI = "https://{api}.googleapis.com/$discovery/rest?labels=DEVELOPER_PREVIEW&version={apiVersion}"
4734
4735
 
4735
4736
  # Used for API.CLOUDRESOURCEMANAGER, API.SERVICEUSAGE, API.IAM
4736
4737
  def getAPIService(api, httpObj):
@@ -4750,8 +4751,13 @@ def getService(api, httpObj):
4750
4751
  triesLimit = 3
4751
4752
  for n in range(1, triesLimit+1):
4752
4753
  try:
4754
+ if api != API.CLASSROOM or not GC.Values[GC.DEVELOPER_PREVIEW_API_KEY]:
4755
+ discoveryServiceUrl = DISCOVERY_URIS[v2discovery]
4756
+ else:
4757
+ discoveryServiceUrl = CLASSROOM_DEVELOPER_PREVIEW_DISCOVERY_URI
4758
+ developerKey = GC.Values[GC.DEVELOPER_PREVIEW_API_KEY]
4753
4759
  service = googleapiclient.discovery.build(api, version, http=httpObj, cache_discovery=False,
4754
- discoveryServiceUrl=DISCOVERY_URIS[v2discovery], static_discovery=False)
4760
+ discoveryServiceUrl=discoveryServiceUrl, developerKey=developerKey, static_discovery=False)
4755
4761
  GM.Globals[GM.CURRENT_API_SERVICES].setdefault(api, {})
4756
4762
  GM.Globals[GM.CURRENT_API_SERVICES][api][version] = service._rootDesc.copy()
4757
4763
  if GM.Globals[GM.CACHE_DISCOVERY_ONLY]:
@@ -26998,31 +27004,33 @@ CHAT_SPACES_ADMIN_ORDERBY_CHOICE_MAP = {
26998
27004
 
26999
27005
  # gam [<UserTypeEntity>] show chatspaces
27000
27006
  # [types <ChatSpaceTypeList>]
27001
- # [fields <ChatSpaceFieldNameList>]
27007
+ # [fields <ChatSpaceFieldNameList>] [showaccesssettings]
27002
27008
  # [formatjson]
27003
27009
  # gam [<UserTypeEntity>] print chatspaces [todrive <ToDriveAttribute>*]
27004
27010
  # [types <ChatSpaceTypeList>]
27005
- # [fields <ChatSpaceFieldNameList>]
27011
+ # [fields <ChatSpaceFieldNameList>] [showaccesssettings]
27006
27012
  # [formatjson [quotechar <Character>]]
27007
27013
  # gam <UserItem> show chatspaces asadmin
27008
27014
  # [query <String>] [querytime<String> <Time>]
27009
27015
  # [orderby <ChatSpaceAdminOrderByFieldName> [ascending|descending]]
27010
- # [fields <ChatSpaceFieldNameList>]
27016
+ # [fields <ChatSpaceFieldNameList>] [showaccesssettings]
27011
27017
  # [formatjson]
27012
27018
  # gam <UserItem> print chatspaces asadmin [todrive <ToDriveAttribute>*]
27013
27019
  # [query <String>] [querytime<String> <Time>]
27014
27020
  # [orderby <ChatSpaceAdminOrderByFieldName> [ascending|descending]]
27015
- # [fields <ChatSpaceFieldNameList>]
27021
+ # [fields <ChatSpaceFieldNameList>] [showaccesssettings]
27016
27022
  # [formatjson [quotechar <Character>]]
27017
27023
  def printShowChatSpaces(users):
27018
27024
  csvPF = CSVPrintFile(['User', 'name'] if not isinstance(users, list) else ['name']) if Act.csvFormat() else None
27019
27025
  FJQC = FormatJSONQuoteChar(csvPF)
27020
27026
  OBY = OrderBy(CHAT_SPACES_ADMIN_ORDERBY_CHOICE_MAP)
27021
27027
  useAdminAccess, api, kwargsCS = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES)
27028
+ kwargsSAS = {'useAdminAccess': useAdminAccess}
27022
27029
  fieldsList = []
27023
27030
  queries = []
27024
27031
  queryTimes = {}
27025
27032
  pfilter = ''
27033
+ showAccessSettings = False
27026
27034
  if useAdminAccess:
27027
27035
  function = 'search'
27028
27036
  queries = ['customer = "customers/my_customer" AND spaceType = "SPACE"']
@@ -27038,8 +27046,12 @@ def printShowChatSpaces(users):
27038
27046
  pass
27039
27047
  elif useAdminAccess and _getChatSpaceSearchParms(myarg, queries, queryTimes, OBY):
27040
27048
  pass
27049
+ elif myarg == 'showaccesssettings':
27050
+ showAccessSettings = True
27041
27051
  else:
27042
27052
  FJQC.GetFormatJSONQuoteChar(myarg, True)
27053
+ if showAccessSettings and fieldsList:
27054
+ fieldsList.extend(['name', 'spaceType'])
27043
27055
  fields = getItemFieldsFromFieldsList('spaces', fieldsList)
27044
27056
  i, count, users = getEntityArgument(users)
27045
27057
  if useAdminAccess:
@@ -27064,6 +27076,17 @@ def printShowChatSpaces(users):
27064
27076
  GAPI.PERMISSION_DENIED, GAPI.FAILED_PRECONDITION],
27065
27077
  retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
27066
27078
  fields=fields, pageSize=CHAT_PAGE_SIZE, **kwargsCS)
27079
+ if showAccessSettings:
27080
+ for space in spaces:
27081
+ if space['spaceType'] == 'SPACE':
27082
+ try:
27083
+ result = callGAPI(chat.spaces(), 'get',
27084
+ throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.INTERNAL_ERROR,
27085
+ GAPI.PERMISSION_DENIED, GAPI.FAILED_PRECONDITION],
27086
+ name=space['name'], fields='accessSettings', **kwargsSAS)
27087
+ space.update(result)
27088
+ except (GAPI.notFound, GAPI.invalidArgument, GAPI.internalError, GAPI.permissionDenied, GAPI.failedPrecondition):
27089
+ pass
27067
27090
  except (GAPI.notFound, GAPI.invalidArgument, GAPI.internalError, GAPI.permissionDenied) as e:
27068
27091
  exitIfChatNotConfigured(chat, kvList, str(e), i, count)
27069
27092
  continue
@@ -48430,6 +48453,14 @@ def _convertCourseUserIdToEmail(croom, userId, emails, entityValueList, i, count
48430
48453
  emails[userId] = userEmail
48431
48454
  return userEmail
48432
48455
 
48456
+ def _getCourseOwnerSA(croom, course, useOwnerAccess):
48457
+ if not useOwnerAccess:
48458
+ return croom
48459
+ courseOwnerId = course["ownerId"]
48460
+ if courseOwnerId not in GM.Globals[GM.CLASSROOM_OWNER_SA]:
48461
+ _, GM.Globals[GM.CLASSROOM_OWNER_SA][courseOwnerId] = buildGAPIServiceObject(API.CLASSROOM, f'uid:{courseOwnerId}')
48462
+ return GM.Globals[GM.CLASSROOM_OWNER_SA][courseOwnerId]
48463
+
48433
48464
  def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess, addCIIdScope=True):
48434
48465
  coursesInfo = {}
48435
48466
  for courseId in courseIds:
@@ -48443,13 +48474,10 @@ def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess, addCIIdScope=True):
48443
48474
  throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
48444
48475
  GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
48445
48476
  retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
48446
- id=courseId, fields='name,ownerId')
48447
- if useOwnerAccess:
48448
- _, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{course["ownerId"]}')
48449
- else:
48450
- ocroom = croom
48477
+ id=courseId, fields='id,name,ownerId')
48478
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
48451
48479
  if ocroom is not None:
48452
- coursesInfo[ciCourseId] = {'name': course['name'], 'croom': ocroom}
48480
+ coursesInfo[ciCourseId] = {'id': course['id'], 'name': course['name'], 'croom': ocroom}
48453
48481
  except GAPI.notFound:
48454
48482
  entityDoesNotExistWarning(Ent.COURSE, courseId)
48455
48483
  except GAPI.serviceNotAvailable as e:
@@ -48836,12 +48864,9 @@ def doPrintCourses():
48836
48864
  for field in courseShowProperties['skips']:
48837
48865
  course.pop(field, None)
48838
48866
  courseId = course['id']
48839
- if useOwnerAccess:
48840
- _, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{course["ownerId"]}')
48841
- if not ocroom:
48842
- continue
48843
- else:
48844
- ocroom = croom
48867
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
48868
+ if not ocroom:
48869
+ continue
48845
48870
  if courseShowProperties['ownerEmail']:
48846
48871
  course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails,
48847
48872
  [Ent.COURSE, courseId, Ent.OWNER_ID, course['ownerId']], i, count)
@@ -49651,12 +49676,9 @@ def doPrintCourseParticipants():
49651
49676
  for course in coursesInfo:
49652
49677
  i += 1
49653
49678
  courseId = course['id']
49654
- if useOwnerAccess:
49655
- _, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{course["ownerId"]}')
49656
- if not ocroom:
49657
- continue
49658
- else:
49659
- ocroom = croom
49679
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
49680
+ if not ocroom:
49681
+ continue
49660
49682
  _, teachers, students = _getCourseAliasesMembers(croom, ocroom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count)
49661
49683
  if showItemCountOnly:
49662
49684
  if courseShowProperties['members'] != 'students':
@@ -51209,6 +51231,461 @@ def printShowClassroomProfile(users):
51209
51231
  if csvPF:
51210
51232
  csvPF.writeCSVfile('Classroom User Profiles')
51211
51233
 
51234
+ # gam create course-studentgroups
51235
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51236
+ # title <String>
51237
+ # [csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]]
51238
+ def doCreateCourseStudentGroups():
51239
+ croom = buildGAPIObject(API.CLASSROOM)
51240
+ csvPF = None
51241
+ FJQC = FormatJSONQuoteChar(None)
51242
+ courseSelectionParameters = _initCourseSelectionParameters()
51243
+ courseShowProperties = _initCourseShowProperties(['name'])
51244
+ useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
51245
+ kwargs = {'courseId': None, 'body': {}}
51246
+ while Cmd.ArgumentsRemaining():
51247
+ myarg = getArgument()
51248
+ if myarg == 'title':
51249
+ kwargs['body']['title'] = getString(Cmd.OB_STRING)
51250
+ elif _getCourseSelectionParameters(myarg, courseSelectionParameters):
51251
+ pass
51252
+ elif myarg == 'csv':
51253
+ csvPF = CSVPrintFile(['courseId', 'courseName', 'studentGroupId', 'studentGroupTitle'])
51254
+ FJQC = FormatJSONQuoteChar(csvPF)
51255
+ elif csvPF and myarg == 'todrive':
51256
+ csvPF.GetTodriveParameters()
51257
+ else:
51258
+ FJQC.GetFormatJSONQuoteChar(myarg, True)
51259
+ if 'title' not in kwargs['body']:
51260
+ missingArgumentExit('title')
51261
+ if csvPF and FJQC.formatJSON:
51262
+ csvPF.SetJSONTitles(['courseId', 'courseName', 'JSON'])
51263
+ coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
51264
+ if coursesInfo is None:
51265
+ return
51266
+ count = len(coursesInfo)
51267
+ i = 0
51268
+ for course in coursesInfo:
51269
+ i += 1
51270
+ courseId = course['id']
51271
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
51272
+ if not ocroom:
51273
+ continue
51274
+ kwargs['courseId'] = courseId
51275
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, None]
51276
+ try:
51277
+ studentGroup = callGAPI(ocroom.courses().studentGroups(), 'create',
51278
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51279
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51280
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51281
+ previewVersion='V1_20250630_PREVIEW',
51282
+ **kwargs)
51283
+ kvList[-1] = f"{studentGroup['title']}({studentGroup['id']})"
51284
+ if not csvPF:
51285
+ entityActionPerformed(kvList, i, count)
51286
+ elif not FJQC.formatJSON:
51287
+ csvPF.WriteRow({'courseId': courseId, 'courseName': course['name'],
51288
+ 'studentGroupId': studentGroup['id'], 'studentGroupTitle': studentGroup['title']})
51289
+ else:
51290
+ csvPF.WriteRowNoFilter({'courseId': courseId, 'courseName': course['name'],
51291
+ 'JSON': json.dumps(cleanJSON(studentGroup), ensure_ascii=False, sort_keys=True)})
51292
+ except GAPI.notFound as e:
51293
+ entityActionFailedWarning(kvList, str(e), i, count)
51294
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51295
+ entityActionFailedExit([Ent.COURSE, courseId], str(e), i, count)
51296
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51297
+ ClientAPIAccessDeniedExit(str(e))
51298
+ if csvPF:
51299
+ csvPF.writeCSVfile('Course Student Groups')
51300
+
51301
+ # gam update course-studentgroup <CourseID> <StudentGroupID> title <String>
51302
+ def doUpdateCourseStudentGroups():
51303
+ croom = buildGAPIObject(API.CLASSROOM)
51304
+ courseId = getString(Cmd.OB_COURSE_ID)
51305
+ studentGroupId = getString(Cmd.OB_STUDENTGROUP_ID)
51306
+ kwargs = {'courseId': courseId, 'id': studentGroupId, 'body': {}, 'updateMask': ''}
51307
+ while Cmd.ArgumentsRemaining():
51308
+ myarg = getArgument()
51309
+ if myarg == 'title':
51310
+ kwargs['body']['title'] = getString(Cmd.OB_STRING)
51311
+ kwargs['updateMask'] = myarg
51312
+ else:
51313
+ unknownArgumentExit()
51314
+ if 'title' not in kwargs['body']:
51315
+ missingArgumentExit('title')
51316
+ _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51317
+ if count == 0:
51318
+ return
51319
+ ocroom = coursesInfo[courseId]['croom']
51320
+ courseId = coursesInfo[courseId]['id']
51321
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId]
51322
+ try:
51323
+ studentGroup = callGAPI(ocroom.courses().studentGroups(), 'patch',
51324
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51325
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51326
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51327
+ previewVersion='V1_20250630_PREVIEW',
51328
+ **kwargs)
51329
+ kvList[-1] = f"{studentGroup['title']}({studentGroup['id']})"
51330
+ entityActionPerformed(kvList)
51331
+ except GAPI.notFound as e:
51332
+ entityActionFailedWarning(kvList, str(e))
51333
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51334
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51335
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51336
+ ClientAPIAccessDeniedExit(str(e))
51337
+
51338
+ # gam delete course-studentgroups <CourseID> <StudentGroupIDEntity>
51339
+ def doDeleteCourseStudentGroups():
51340
+ croom = buildGAPIObject(API.CLASSROOM)
51341
+ courseId = getString(Cmd.OB_COURSE_ID)
51342
+ studentGroupIds = getEntityList(Cmd.OB_STUDENTGROUP_ID_ENTITY, shlexSplit=False)
51343
+ checkForExtraneousArguments()
51344
+ _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51345
+ if count == 0:
51346
+ return
51347
+ ocroom = coursesInfo[courseId]['croom']
51348
+ courseId = coursesInfo[courseId]['id']
51349
+ kwargs = {'courseId': courseId, 'id': None}
51350
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, None]
51351
+ count = len(studentGroupIds)
51352
+ i = 0
51353
+ for studentGroupId in studentGroupIds:
51354
+ i += 1
51355
+ kwargs['id'] = kvList[-1] = studentGroupId
51356
+ try:
51357
+ callGAPI(ocroom.courses().studentGroups(), 'delete',
51358
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51359
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51360
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51361
+ previewVersion='V1_20250630_PREVIEW',
51362
+ **kwargs)
51363
+ entityActionPerformed(kvList, i, count)
51364
+ except GAPI.notFound as e:
51365
+ entityActionFailedWarning(kvList, str(e), i, count)
51366
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51367
+ entityActionFailedExit([Ent.COURSE, courseId], str(e), i, count)
51368
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51369
+ ClientAPIAccessDeniedExit(str(e))
51370
+
51371
+ # gam clear course-studentgroups
51372
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51373
+ def doClearCourseStudentGroups():
51374
+ croom = buildGAPIObject(API.CLASSROOM)
51375
+ courseSelectionParameters = _initCourseSelectionParameters()
51376
+ courseShowProperties = _initCourseShowProperties(['name'])
51377
+ useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
51378
+ while Cmd.ArgumentsRemaining():
51379
+ myarg = getArgument()
51380
+ if _getCourseSelectionParameters(myarg, courseSelectionParameters):
51381
+ pass
51382
+ else:
51383
+ unknownArgumentExit()
51384
+ coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
51385
+ if coursesInfo is None:
51386
+ return
51387
+ count = len(coursesInfo)
51388
+ i = 0
51389
+ for course in coursesInfo:
51390
+ i += 1
51391
+ courseId = course['id']
51392
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
51393
+ if not ocroom:
51394
+ continue
51395
+ kwargs = {'courseId': courseId, 'id': None}
51396
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, None]
51397
+ studentGroups = []
51398
+ printGettingEntityItemForWhom(Ent.COURSE_STUDENTGROUP, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count)))
51399
+ pageMessage = getPageMessage()
51400
+ try:
51401
+ studentGroups = callGAPIpages(ocroom.courses().studentGroups(), 'list', 'studentGroups',
51402
+ pageMessage=pageMessage,
51403
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51404
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51405
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51406
+ previewVersion='V1_20250630_PREVIEW',
51407
+ courseId=courseId)
51408
+ except GAPI.notFound as e:
51409
+ entityActionFailedWarning([Ent.COURSE, courseId], str(e))
51410
+ continue
51411
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51412
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51413
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51414
+ ClientAPIAccessDeniedExit(str(e))
51415
+ jcount = len(studentGroups)
51416
+ entityPerformActionNumItems([Ent.COURSE, courseId], jcount, Ent.COURSE_STUDENTGROUP, i, count)
51417
+ Ind.Increment()
51418
+ j = 0
51419
+ for studentGroup in studentGroups:
51420
+ j += 1
51421
+ kwargs['id'] = kvList[-1] = studentGroup['id']
51422
+ try:
51423
+ callGAPI(ocroom.courses().studentGroups(), 'delete',
51424
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51425
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51426
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51427
+ previewVersion='V1_20250630_PREVIEW',
51428
+ **kwargs)
51429
+ entityActionPerformed(kvList, j, jcount)
51430
+ except GAPI.notFound as e:
51431
+ entityActionFailedWarning(kvList, str(e), j, jcount)
51432
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51433
+ entityActionFailedExit([Ent.COURSE, courseId], str(e), j, jcount)
51434
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51435
+ ClientAPIAccessDeniedExit(str(e))
51436
+ Ind.Decrement()
51437
+
51438
+ # gam print course-studentgroups [todrive <ToDriveAttribute>*]
51439
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51440
+ # [showitemcountonly] [formatjson [quotechar <Character>]]
51441
+ def doPrintCourseStudentGroups(showMembers=False):
51442
+ croom = buildGAPIObject(API.CLASSROOM)
51443
+ cd = buildGAPIObject(API.DIRECTORY)
51444
+ csvPF = CSVPrintFile(['courseId', 'courseName', 'studentGroupId', 'studentGroupTitle'])
51445
+ FJQC = FormatJSONQuoteChar(csvPF)
51446
+ courseSelectionParameters = _initCourseSelectionParameters()
51447
+ courseShowProperties = _initCourseShowProperties(['name'])
51448
+ showItemCountOnly = False
51449
+ useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
51450
+ while Cmd.ArgumentsRemaining():
51451
+ myarg = getArgument()
51452
+ if myarg == 'todrive':
51453
+ csvPF.GetTodriveParameters()
51454
+ elif _getCourseSelectionParameters(myarg, courseSelectionParameters):
51455
+ pass
51456
+ elif myarg == 'showitemcountonly':
51457
+ showItemCountOnly = True
51458
+ else:
51459
+ FJQC.GetFormatJSONQuoteChar(myarg, True)
51460
+ coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
51461
+ if coursesInfo is None:
51462
+ if showItemCountOnly:
51463
+ writeStdout('0\n')
51464
+ return
51465
+ itemCount = 0
51466
+ count = len(coursesInfo)
51467
+ i = 0
51468
+ for course in coursesInfo:
51469
+ i += 1
51470
+ courseId = course['id']
51471
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
51472
+ if not ocroom:
51473
+ continue
51474
+ studentGroups = []
51475
+ printGettingEntityItemForWhom(Ent.COURSE_STUDENTGROUP, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count)))
51476
+ pageMessage = getPageMessage()
51477
+ try:
51478
+ studentGroups = callGAPIpages(ocroom.courses().studentGroups(), 'list', 'studentGroups',
51479
+ pageMessage=pageMessage,
51480
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51481
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51482
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51483
+ previewVersion='V1_20250630_PREVIEW',
51484
+ courseId=courseId)
51485
+ except GAPI.notFound as e:
51486
+ entityActionFailedWarning([Ent.COURSE, courseId], str(e))
51487
+ continue
51488
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51489
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51490
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51491
+ ClientAPIAccessDeniedExit(str(e))
51492
+ if not showMembers and showItemCountOnly:
51493
+ itemCount += len(studentGroups)
51494
+ continue
51495
+ for studentGroup in studentGroups:
51496
+ studentGroupId = studentGroup['id']
51497
+ if not showMembers:
51498
+ if not FJQC.formatJSON:
51499
+ csvPF.WriteRowTitles({'courseId': courseId, 'courseName': course['name'],
51500
+ 'studentGroupId': studentGroupId, 'studentGroupTitle': studentGroup['title']})
51501
+ else:
51502
+ row = {'courseId': courseId, 'courseName': course['name']}
51503
+ if csvPF.CheckRowTitles(row):
51504
+ row['JSON'] = json.dumps(cleanJSON(studentGroup), ensure_ascii=False, sort_keys=False)
51505
+ csvPF.WriteRowTitles(row)
51506
+ continue
51507
+ printGettingEntityItemForWhom(Ent.USER, formatKeyValueList('', [Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId],
51508
+ currentCount(i, count)))
51509
+ pageMessage = getPageMessage()
51510
+ try:
51511
+ students = callGAPIpages(ocroom.courses().studentGroups().studentGroupMembers(), 'list', 'studentGroupMembers',
51512
+ pageMessage=pageMessage,
51513
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
51514
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51515
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51516
+ previewVersion='V1_20250630_PREVIEW',
51517
+ courseId=courseId, studentGroupId=studentGroupId)
51518
+ if showItemCountOnly:
51519
+ itemCount += len(students)
51520
+ continue
51521
+ for member in students:
51522
+ member['userEmail'] = convertUIDtoEmailAddress(f"id:{member['userId']}", cd=cd, emailTypes=['user'])
51523
+ except GAPI.notFound as e:
51524
+ entityActionFailedWarning([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51525
+ continue
51526
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51527
+ entityActionFailedExit([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51528
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51529
+ ClientAPIAccessDeniedExit(str(e))
51530
+ if not FJQC.formatJSON:
51531
+ row = {'courseId': courseId, 'courseName': course['name'],
51532
+ 'studentGroupId': studentGroupId, 'studentGroupTitle': studentGroup['title']}
51533
+ for member in students:
51534
+ csvPF.WriteRowTitles(flattenJSON(member, flattened=row.copy()))
51535
+ else:
51536
+ row = {'courseId': courseId, 'courseName': course['name'],
51537
+ 'studentGroupId': studentGroupId, 'studentGroupTitle': studentGroup['title']}
51538
+ row['JSON'] = json.dumps(list(students))
51539
+ csvPF.WriteRowNoFilter(row)
51540
+ if showItemCountOnly:
51541
+ writeStdout(f'{itemCount}\n')
51542
+ return
51543
+ if not showMembers:
51544
+ title = 'Course Student Groups'
51545
+ else:
51546
+ title = 'Course Student Group Members'
51547
+ csvPF.writeCSVfile(title)
51548
+
51549
+ # gam create course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51550
+ # gam delete course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51551
+ # gam sync course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51552
+ # gam clear course-studentgroup-members <CourseID> <StudentGroupID>
51553
+ def doProcessCourseStudentGroupMembers():
51554
+ def _getCurrentStudents():
51555
+ printGettingEntityItemForWhom(Ent.USER, Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId)
51556
+ pageMessage = getPageMessage()
51557
+ try:
51558
+ return callGAPIpages(ocroom.courses().studentGroups().studentGroupMembers(), 'list', 'studentGroupMembers',
51559
+ pageMessage=pageMessage,
51560
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
51561
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51562
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51563
+ previewVersion='V1_20250630_PREVIEW',
51564
+ courseId=courseId, studentGroupId=studentGroupId)
51565
+ except GAPI.notFound as e:
51566
+ entityActionFailedWarning([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51567
+ return []
51568
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51569
+ entityActionFailedExit([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51570
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51571
+ ClientAPIAccessDeniedExit(str(e))
51572
+
51573
+ def _getStudentUserId(kvList, student, i, count):
51574
+ normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(student)
51575
+ if normalizedEmailAddressOrUID.find('@') == -1:
51576
+ return normalizedEmailAddressOrUID
51577
+ try:
51578
+ return callGAPI(cd.users(), 'get',
51579
+ throwReasons=GAPI.USER_GET_THROW_REASONS,
51580
+ userKey=normalizedEmailAddressOrUID, fields='id')['id']
51581
+ except (GAPI.userNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
51582
+ GAPI.badRequest, GAPI.backendError, GAPI.systemError) as e:
51583
+ entityActionFailedWarning(kvList, str(e), i, count)
51584
+ return None
51585
+
51586
+ def _processStudent(function, kvList, kwargs, i, count):
51587
+ try:
51588
+ callGAPI(ocroom.courses().studentGroups().studentGroupMembers(), function,
51589
+ throwReasons=[GAPI.NOT_FOUND, GAPI.ALREADY_EXISTS,
51590
+ GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51591
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51592
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51593
+ previewVersion='V1_20250630_PREVIEW',
51594
+ **kwargs)
51595
+ entityActionPerformed(kvList, i, count)
51596
+ except (GAPI.alreadyExists, GAPI.notFound) as e:
51597
+ entityActionFailedWarning(kvList, str(e), i, count)
51598
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51599
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51600
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51601
+ ClientAPIAccessDeniedExit(str(e))
51602
+
51603
+ def _addStudents(students, getUserIds):
51604
+ count = len(students)
51605
+ i = 0
51606
+ entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.USER)
51607
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51608
+ kwargs = {'courseId': courseId, 'studentGroupId': studentGroupId, 'body': {'userId': ''}}
51609
+ for student in students:
51610
+ i += 1
51611
+ if getUserIds:
51612
+ userId = _getStudentUserId(kvList, student, i, count)
51613
+ if userId is None:
51614
+ continue
51615
+ kvList[-1] = student
51616
+ else:
51617
+ userId = student
51618
+ kvList[-1] = convertUIDtoEmailAddress(f"id:{userId}", cd=cd, emailTypes=['user'])
51619
+ kwargs['body']['userId'] = userId
51620
+ _processStudent('create', kvList, kwargs, i, count)
51621
+
51622
+ def _removeStudents(students, getUserIds):
51623
+ count = len(students)
51624
+ i = 0
51625
+ entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.USER)
51626
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51627
+ kwargs = {'courseId': courseId, 'studentGroupId': studentGroupId, 'userId': ''}
51628
+ for student in students:
51629
+ i += 1
51630
+ if getUserIds:
51631
+ userId = _getStudentUserId(kvList, student, i, count)
51632
+ if userId is None:
51633
+ continue
51634
+ kvList[-1] = student
51635
+ else:
51636
+ userId = student
51637
+ kvList[-1] = convertUIDtoEmailAddress(f"id:{userId}", cd=cd, emailTypes=['user'])
51638
+ kwargs['userId'] = userId
51639
+ _processStudent('delete', kvList, kwargs, i, count)
51640
+
51641
+ croom = buildGAPIObject(API.CLASSROOM)
51642
+ cd = buildGAPIObject(API.DIRECTORY)
51643
+ action = Act.Get()
51644
+ courseId = getString(Cmd.OB_COURSE_ID)
51645
+ studentGroupId = getString(Cmd.OB_STUDENTGROUP_ID)
51646
+ if action != Act.CLEAR:
51647
+ _, clStudents = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS, groupMemberType=Ent.TYPE_USER)
51648
+ clStudents = [normalizeEmailAddressOrUID(student) for student in clStudents]
51649
+ checkForExtraneousArguments()
51650
+ _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51651
+ if count == 0:
51652
+ return
51653
+ ocroom = coursesInfo[courseId]['croom']
51654
+ courseId = coursesInfo[courseId]['id']
51655
+ if action in {Act.SYNC, Act.CLEAR}:
51656
+ currentStudents = [student['userId'] for student in _getCurrentStudents()]
51657
+ if action == Act.CLEAR:
51658
+ _removeStudents(currentStudents, False)
51659
+ elif action == Act.DELETE:
51660
+ _removeStudents(clStudents, True)
51661
+ elif action in {Act.ADD, Act.CREATE}:
51662
+ _addStudents(clStudents, True)
51663
+ else: # elif action == Act.SYNC:
51664
+ currentMembersSet = set(currentStudents)
51665
+ syncMembersSet = set()
51666
+ count = len(clStudents)
51667
+ i = 0
51668
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51669
+ for student in clStudents:
51670
+ i += 1
51671
+ kvList[-1] = student
51672
+ userId = _getStudentUserId(kvList, student, i, count)
51673
+ if userId is None:
51674
+ continue
51675
+ syncMembersSet.add(userId)
51676
+ removeStudentsSet = currentMembersSet-syncMembersSet
51677
+ addStudentsSet = syncMembersSet-currentMembersSet
51678
+ Act.Set(Act.DELETE)
51679
+ _removeStudents(removeStudentsSet, False)
51680
+ Act.Set(Act.ADD)
51681
+ _addStudents(addStudentsSet, False)
51682
+
51683
+ # gam print course-studentgroup-members [todrive <ToDriveAttribute>*]
51684
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51685
+ # [showitemcountonly] [formatjson [quotechar <Character>]]
51686
+ def doPrintCourseStudentGroupMembers():
51687
+ doPrintCourseStudentGroups(showMembers=True)
51688
+
51212
51689
  def _showASPs(user, asps, i=0, count=0):
51213
51690
  Act.Set(Act.SHOW)
51214
51691
  jcount = len(asps)
@@ -77337,6 +77814,8 @@ MAIN_ADD_CREATE_FUNCTIONS = {
77337
77814
  Cmd.ARG_CIGROUP: doCreateCIGroup,
77338
77815
  Cmd.ARG_CONTACT: doCreateDomainContact,
77339
77816
  Cmd.ARG_COURSE: doCreateCourse,
77817
+ Cmd.ARG_COURSESTUDENTGROUP: doCreateCourseStudentGroups,
77818
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77340
77819
  Cmd.ARG_DATATRANSFER: doCreateDataTransfer,
77341
77820
  Cmd.ARG_DEVICE: doCreateCIDevice,
77342
77821
  Cmd.ARG_DOMAIN: doCreateDomain,
@@ -77412,6 +77891,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77412
77891
  (Act.CLEAR,
77413
77892
  {Cmd.ARG_ALERTSETTINGS: doClearAlertSettings,
77414
77893
  Cmd.ARG_CONTACT: doClearDomainContacts,
77894
+ Cmd.ARG_COURSESTUDENTGROUP: doClearCourseStudentGroups,
77895
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77415
77896
  }
77416
77897
  ),
77417
77898
  'close':
@@ -77454,6 +77935,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77454
77935
  Cmd.ARG_CONTACTPHOTO: doDeleteDomainContactPhoto,
77455
77936
  Cmd.ARG_COURSE: doDeleteCourse,
77456
77937
  Cmd.ARG_COURSES: doDeleteCourses,
77938
+ Cmd.ARG_COURSESTUDENTGROUP: doDeleteCourseStudentGroups,
77939
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77457
77940
  Cmd.ARG_DEVICE: doDeleteCIDevice,
77458
77941
  Cmd.ARG_DEVICEUSER: doDeleteCIDeviceUser,
77459
77942
  Cmd.ARG_DOMAIN: doDeleteDomain,
@@ -77631,6 +78114,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77631
78114
  Cmd.ARG_COURSEANNOUNCEMENTS: doPrintCourseAnnouncements,
77632
78115
  Cmd.ARG_COURSEMATERIALS: doPrintCourseMaterials,
77633
78116
  Cmd.ARG_COURSEPARTICIPANTS: doPrintCourseParticipants,
78117
+ Cmd.ARG_COURSESTUDENTGROUP: doPrintCourseStudentGroups,
78118
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doPrintCourseStudentGroupMembers,
77634
78119
  Cmd.ARG_COURSESUBMISSIONS: doPrintCourseSubmissions,
77635
78120
  Cmd.ARG_COURSETOPICS: doPrintCourseTopics,
77636
78121
  Cmd.ARG_COURSEWORK: doPrintCourseWork,
@@ -77815,6 +78300,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77815
78300
  (Act.SYNC,
77816
78301
  {Cmd.ARG_DEVICE: doSyncCIDevices,
77817
78302
  Cmd.ARG_SHAREDDRIVEACLS: copySyncSharedDriveACLs,
78303
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77818
78304
  }
77819
78305
  ),
77820
78306
  'unhide':
@@ -77837,6 +78323,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77837
78323
  Cmd.ARG_CONTACTPHOTO: doUpdateDomainContactPhoto,
77838
78324
  Cmd.ARG_COURSE: doUpdateCourse,
77839
78325
  Cmd.ARG_COURSES: doUpdateCourses,
78326
+ Cmd.ARG_COURSESTUDENTGROUP: doUpdateCourseStudentGroups,
77840
78327
  Cmd.ARG_CROS: doUpdateCrOSDevices,
77841
78328
  Cmd.ARG_CUSTOMER: doUpdateCustomer,
77842
78329
  Cmd.ARG_DEVICE: doUpdateCIDevice,
@@ -77954,6 +78441,7 @@ MAIN_COMMANDS_OBJ_ALIASES = {
77954
78441
  Cmd.ARG_CLASSROOMINVITATIONS: Cmd.ARG_CLASSROOMINVITATION,
77955
78442
  Cmd.ARG_CONTACTS: Cmd.ARG_CONTACT,
77956
78443
  Cmd.ARG_CONTACTPHOTOS: Cmd.ARG_CONTACTPHOTO,
78444
+ Cmd.ARG_COURSESTUDENTGROUPS: Cmd.ARG_COURSESTUDENTGROUP,
77957
78445
  Cmd.ARG_CROSES: Cmd.ARG_CROS,
77958
78446
  Cmd.ARG_DATATRANSFERS: Cmd.ARG_DATATRANSFER,
77959
78447
  Cmd.ARG_DEVICES: Cmd.ARG_DEVICE,
gam/gamlib/glcfg.py CHANGED
@@ -145,6 +145,8 @@ CSV_OUTPUT_USERS_AUDIT = 'csv_output_users_audit'
145
145
  CUSTOMER_ID = 'customer_id'
146
146
  # If debug_level > 0: extra_args['prettyPrint'] = True, httplib2.debuglevel = gam_debug_level, appsObj.debug = True
147
147
  DEBUG_LEVEL = 'debug_level'
148
+ # Developer Preview API Key
149
+ DEVELOPER_PREVIEW_API_KEY = 'developer_preview_api_key'
148
150
  # When retrieving lists of ChromeOS devices from API, how many should be retrieved in each chunk
149
151
  DEVICE_MAX_RESULTS = 'device_max_results'
150
152
  # Domain obtained from gam.cfg or oauth2.txt
@@ -370,6 +372,7 @@ Defaults = {
370
372
  CSV_OUTPUT_USERS_AUDIT: FALSE,
371
373
  CUSTOMER_ID: MY_CUSTOMER,
372
374
  DEBUG_LEVEL: '0',
375
+ DEVELOPER_PREVIEW_API_KEY: '',
373
376
  DEVICE_MAX_RESULTS: '200',
374
377
  DOMAIN: '',
375
378
  DRIVE_DIR: '',
@@ -536,6 +539,7 @@ VAR_INFO = {
536
539
  CSV_OUTPUT_USERS_AUDIT: {VAR_TYPE: TYPE_BOOLEAN},
537
540
  CUSTOMER_ID: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'CUSTOMER_ID', VAR_LIMITS: (0, None)},
538
541
  DEBUG_LEVEL: {VAR_TYPE: TYPE_INTEGER, VAR_SIGFILE: 'debug.gam', VAR_LIMITS: (0, None), VAR_SFFT: ('0', '4')},
542
+ DEVELOPER_PREVIEW_API_KEY: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
539
543
  DEVICE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
540
544
  DOMAIN: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GA_DOMAIN', VAR_LIMITS: (0, None)},
541
545
  DRIVE_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMDRIVEDIR'},
gam/gamlib/glclargs.py CHANGED
@@ -528,6 +528,9 @@ class GamCLArgs():
528
528
  ARG_COURSEANNOUNCEMENTS = 'courseannouncements'
529
529
  ARG_COURSEMATERIALS = 'coursematerials'
530
530
  ARG_COURSEPARTICIPANTS = 'courseparticipants'
531
+ ARG_COURSESTUDENTGROUP = 'coursestudentgroup'
532
+ ARG_COURSESTUDENTGROUPS = 'coursestudentgroups'
533
+ ARG_COURSESTUDENTGROUPMEMBERS = 'coursestudentgroupmembers'
531
534
  ARG_COURSESUBMISSIONS = 'coursesubmissions'
532
535
  ARG_COURSETOPICS = 'coursetopics'
533
536
  ARG_COURSEWORK = 'coursework'
@@ -1055,6 +1058,8 @@ class GamCLArgs():
1055
1058
  OB_STATE_NAME_LIST = "StateNameList"
1056
1059
  OB_STRING = 'String'
1057
1060
  OB_STRING_LIST = 'StringList'
1061
+ OB_STUDENTGROUP_ID = 'StudentGroupID'
1062
+ OB_STUDENTGROUP_ID_ENTITY = 'StudentGroupIDEntity'
1058
1063
  OB_STUDENT_ITEM = 'StudentItem'
1059
1064
  OB_TAG = 'Tag'
1060
1065
  OB_TAGMANAGER_PATH_LIST = 'TagManagerPathList'
gam/gamlib/glentity.py CHANGED
@@ -155,6 +155,8 @@ class GamEntity():
155
155
  COURSE_MATERIAL_STATE = 'cmst'
156
156
  COURSE_NAME = 'cona'
157
157
  COURSE_STATE = 'cost'
158
+ COURSE_STUDENTGROUP = 'cosg'
159
+ COURSE_STUDENTGROUP_MEMBER = 'csgm'
158
160
  COURSE_SUBMISSION_ID = 'csid'
159
161
  COURSE_SUBMISSION_STATE = 'csst'
160
162
  COURSE_TOPIC = 'ctop'
@@ -517,6 +519,8 @@ class GamEntity():
517
519
  COURSE_MATERIAL_STATE: ['Course Material States', 'Course Material State'],
518
520
  COURSE_NAME: ['Course Names', 'Course Name'],
519
521
  COURSE_STATE: ['Course States', 'Course State'],
522
+ COURSE_STUDENTGROUP: ['Course Student Groups', 'Course Student Group'],
523
+ COURSE_STUDENTGROUP_MEMBER: ['Course Student Group Members', 'Course Student Group Member'],
520
524
  COURSE_SUBMISSION_ID: ['Course Submission IDs', 'Course Submission ID'],
521
525
  COURSE_SUBMISSION_STATE: ['Course Submission States', 'Course Submission State'],
522
526
  COURSE_TOPIC: ['Course Topics', 'Course Topic'],
gam/gamlib/glglobals.py CHANGED
@@ -31,6 +31,8 @@ API_CALLS_RETRY_DATA = 'rtry'
31
31
  CACHE_DIR = 'gacd'
32
32
  # Reset GAM cache directory after discovery
33
33
  CACHE_DISCOVERY_ONLY = 'gcdo'
34
+ # Classroom owner service object
35
+ CLASSROOM_OWNER_SA = 'cosa'
34
36
  # Classroom service not available
35
37
  CLASSROOM_SERVICE_NOT_AVAILABLE = 'csna'
36
38
  # Command logging
@@ -221,6 +223,7 @@ Globals = {
221
223
  API_CALLS_RETRY_DATA: {},
222
224
  CACHE_DIR: None,
223
225
  CACHE_DISCOVERY_ONLY: True,
226
+ CLASSROOM_OWNER_SA: {},
224
227
  CLASSROOM_SERVICE_NOT_AVAILABLE: False,
225
228
  CMDLOG_HANDLER: None,
226
229
  CMDLOG_LOGGER: None,
gam/gamlib/glmsgs.py CHANGED
@@ -53,7 +53,7 @@ Please go to:
53
53
  5. Click "NEXT"
54
54
  6. Under "Audience", choose INTERNAL
55
55
  7. Click "NEXT"
56
- 8. Under, "Contact Information", enter an email address in "Email addresses *"
56
+ 8. Under, "Contact Information", enter {2} or another value in "Email addresses *"
57
57
  9. Click "NEXT"
58
58
  10. Under "Finish", click "I agree to the Google API Services: User Data Policy."
59
59
  11. Click "CONTINUE"
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "2.164.0"
15
+ __version__ = "2.179.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gam7
3
- Version: 7.19.3
3
+ Version: 7.20.1
4
4
  Summary: CLI tool to manage Google Workspace
5
5
  Project-URL: Homepage, https://github.com/GAM-team/GAM
6
6
  Project-URL: Issues, https://github.com/GAM-team/GAM/issues
@@ -1,4 +1,4 @@
1
- gam/__init__.py,sha256=ngnkOfi7vZjdRs83cEbcphscUZiEoju6GlIDSBF9nVM,3584315
1
+ gam/__init__.py,sha256=sa-yzi7R7kgKEULVunOY9AKzQbWAZfX9XH3tRPwGefg,3608416
2
2
  gam/__main__.py,sha256=amz0-959ph6zkZKqjaar4n60yho-T37w6qWI36qx0CA,1049
3
3
  gam/cacerts.pem,sha256=DUsVo2XlFYwfkhe3gnxa-Km4Z4noz74hSApXwTT-nQE,44344
4
4
  gam/cbcm-v1.1beta1.json,sha256=xO5XloCQQULmPbFBx5bckdqmbLFQ7sJ2TImhE1ysDIY,19439
@@ -24,14 +24,14 @@ gam/atom/url.py,sha256=pxO1TlORxyKQTQ1bkBE1unFzjnv9c8LjJkm-UEORShY,4276
24
24
  gam/gamlib/__init__.py,sha256=z5mF-y0j8pm-YNFBaiuxB4M_GAUPG-cXWwrhYwrVReM,679
25
25
  gam/gamlib/glaction.py,sha256=1Il_HrChVnPkzZwiZs5au4mFQVtq4K1Z42uIuR6qdnI,9419
26
26
  gam/gamlib/glapi.py,sha256=u97M7Y2BeP3tYEEGYEz-9kY4fdV0fYkeqC3YN-sRwNc,36028
27
- gam/gamlib/glcfg.py,sha256=87OwNxPbZy9nZr9n-12RSqTYai3zsZNYtR5WBaia6Y0,27906
28
- gam/gamlib/glclargs.py,sha256=pgomQOKbiKOI5TfdC0hTT5BqGbQcikDMRfkUk6DEXrM,43881
29
- gam/gamlib/glentity.py,sha256=ZQTdjVtGe8soMlgcSHJIpLHjQ_UtUL9qfw8ux-C4kik,34855
27
+ gam/gamlib/glcfg.py,sha256=7Ut-7sDTw-WVHZfvDWn_dlVNfuWd6VsPDBpQ3qnyzJE,28100
28
+ gam/gamlib/glclargs.py,sha256=9Oze97BT_GS2WXf4LYg4JlSCmZ_8fG2a33Ft1_pX43k,44134
29
+ gam/gamlib/glentity.py,sha256=8F98YR1ronheiQShD6WHlY-YnBKfMDrc33iRuRoLIvo,35097
30
30
  gam/gamlib/glgapi.py,sha256=pdBbwNtnCwFWxJGaP-_3hdTjSNoOCJF2yo76WdQOi1k,40426
31
31
  gam/gamlib/glgdata.py,sha256=weRppttWm6uRyqtBoGPKoHiNZ2h28nhfUV4J_mbCszY,2707
32
- gam/gamlib/glglobals.py,sha256=J0xcHggVrUBzHJ5GruenKV-qV1zPKcK2qWgAgN3i5Jw,9608
32
+ gam/gamlib/glglobals.py,sha256=oJfaLUQj46XqwrOzRfWhGqO0f1P26xjJZefaILJUFGE,9695
33
33
  gam/gamlib/glindent.py,sha256=RfBa2LDfLIqPLL5vMfC689TCVmqn8xf-qulSzkiatrc,1228
34
- gam/gamlib/glmsgs.py,sha256=vephDvTNbv55f79QzZWEKDcBTXr8knrwDxvx-1TYM6M,33997
34
+ gam/gamlib/glmsgs.py,sha256=uluaFxw5YCG1so9uvOYU6HvAwGJNqJcB1curD8hH-G0,34001
35
35
  gam/gamlib/glskus.py,sha256=e1u3zw1MGQjBgAFXqjrGWQl2d7eYpVlMYGpIKNwjskQ,15360
36
36
  gam/gamlib/gluprop.py,sha256=IyPLCyvn7-NHTUenM71YPQPXRZXx6CB5q-GtJ-FYd1c,11461
37
37
  gam/gamlib/glverlibs.py,sha256=xoQXiwcE_-HVYKv-VYA8O0mazRsc9mN-_ysj1dAlMyc,992
@@ -58,15 +58,15 @@ gam/googleapiclient/http.py,sha256=ITE51oqDBqN1-AA5D-Tnlj3egGc_5O0V5xSzBw3UTKI,6
58
58
  gam/googleapiclient/mimeparse.py,sha256=wwouQMCjppTocJtiQhkkTa27kocYwlFRALL2z11Xo1Y,6530
59
59
  gam/googleapiclient/model.py,sha256=NQDO1GhOGNVCJlSSCLOecdA11yf8RDXfSLFxYb3R7EE,14085
60
60
  gam/googleapiclient/schema.py,sha256=rR3u8WPQ_V8a7GCUsNuvtf6GxzwuMO0HaqsTBp3tnyM,10414
61
- gam/googleapiclient/version.py,sha256=vY1VaLgft_SIONbdSFPvHzipKt1agXbOX-Z-ZFbo9s0,599
61
+ gam/googleapiclient/version.py,sha256=CQnalMsxpdK1_gw_iADQK3D0RNBQ2vMlzw3PbyNFWC4,599
62
62
  gam/googleapiclient/discovery_cache/__init__.py,sha256=ww_vl0vhVLuHSEdRTv3-gq6EDG--Ff7rILYHHFifnzc,2315
63
63
  gam/googleapiclient/discovery_cache/appengine_memcache.py,sha256=6T1pQj-toAhDwfgLuiggFGhxKNGw5y-NnLUzLIF_M4s,1657
64
64
  gam/googleapiclient/discovery_cache/base.py,sha256=yCDPtxnbNN-p5_9fzBacC6P3wcUPlaCQIy5v_dXTons,1389
65
65
  gam/googleapiclient/discovery_cache/file_cache.py,sha256=sim3Mg4HgRYo3vX75jvcKy_aV568EvIrtBfvfbw-044,4774
66
66
  gam/iso8601/__init__.py,sha256=Z2PsYbXgAH5a5xzUvgczCboPzqWpm65kRcIngCnhViU,1218
67
67
  gam/iso8601/iso8601.py,sha256=Li2FHZ4sBTWuthuQhyCvmvj0j6At8JbGzkSv2fc2RHU,4384
68
- gam7-7.19.3.dist-info/METADATA,sha256=TeUdQCPXnItSyqXwJbMSkU3f43C-2H1wKRyc2AF7YPM,2940
69
- gam7-7.19.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
- gam7-7.19.3.dist-info/entry_points.txt,sha256=HVUM5J7dA8YwvJfG30jiLefR19ExMs387TWugWd9sf4,42
71
- gam7-7.19.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
- gam7-7.19.3.dist-info/RECORD,,
68
+ gam7-7.20.1.dist-info/METADATA,sha256=7C53COsDbo2DNmBB177TsMQBp3Y_fgnMnx88aqQXuHg,2940
69
+ gam7-7.20.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
+ gam7-7.20.1.dist-info/entry_points.txt,sha256=HVUM5J7dA8YwvJfG30jiLefR19ExMs387TWugWd9sf4,42
71
+ gam7-7.20.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
+ gam7-7.20.1.dist-info/RECORD,,
File without changes