gam7 7.19.2__py3-none-any.whl → 7.20.0__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.02'
28
+ __version__ = '7.20.00'
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]:
@@ -5807,7 +5813,7 @@ def convertUIDtoEmailAddress(emailAddressOrUID, cd=None, emailTypes=None,
5807
5813
  normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(emailAddressOrUID, ciGroupsAPI=ciGroupsAPI)
5808
5814
  if normalizedEmailAddressOrUID.startswith(NON_EMAIL_MEMBER_PREFIXES):
5809
5815
  return normalizedEmailAddressOrUID
5810
- email, _ = convertUIDtoEmailAddressWithType(emailAddressOrUID, cd, emailTypes,
5816
+ email, _ = convertUIDtoEmailAddressWithType(emailAddressOrUID, cd, None, emailTypes,
5811
5817
  checkForCustomerId, ciGroupsAPI, aliasAllowed)
5812
5818
  return email
5813
5819
 
@@ -48430,6 +48436,14 @@ def _convertCourseUserIdToEmail(croom, userId, emails, entityValueList, i, count
48430
48436
  emails[userId] = userEmail
48431
48437
  return userEmail
48432
48438
 
48439
+ def _getCourseOwnerSA(croom, course, useOwnerAccess):
48440
+ if not useOwnerAccess:
48441
+ return croom
48442
+ courseOwnerId = course["ownerId"]
48443
+ if courseOwnerId not in GM.Globals[GM.CLASSROOM_OWNER_SA]:
48444
+ _, GM.Globals[GM.CLASSROOM_OWNER_SA][courseOwnerId] = buildGAPIServiceObject(API.CLASSROOM, f'uid:{courseOwnerId}')
48445
+ return GM.Globals[GM.CLASSROOM_OWNER_SA][courseOwnerId]
48446
+
48433
48447
  def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess, addCIIdScope=True):
48434
48448
  coursesInfo = {}
48435
48449
  for courseId in courseIds:
@@ -48443,13 +48457,10 @@ def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess, addCIIdScope=True):
48443
48457
  throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
48444
48458
  GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
48445
48459
  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
48460
+ id=courseId, fields='id,name,ownerId')
48461
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
48451
48462
  if ocroom is not None:
48452
- coursesInfo[ciCourseId] = {'name': course['name'], 'croom': ocroom}
48463
+ coursesInfo[ciCourseId] = {'id': course['id'], 'name': course['name'], 'croom': ocroom}
48453
48464
  except GAPI.notFound:
48454
48465
  entityDoesNotExistWarning(Ent.COURSE, courseId)
48455
48466
  except GAPI.serviceNotAvailable as e:
@@ -48836,12 +48847,9 @@ def doPrintCourses():
48836
48847
  for field in courseShowProperties['skips']:
48837
48848
  course.pop(field, None)
48838
48849
  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
48850
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
48851
+ if not ocroom:
48852
+ continue
48845
48853
  if courseShowProperties['ownerEmail']:
48846
48854
  course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails,
48847
48855
  [Ent.COURSE, courseId, Ent.OWNER_ID, course['ownerId']], i, count)
@@ -49651,12 +49659,9 @@ def doPrintCourseParticipants():
49651
49659
  for course in coursesInfo:
49652
49660
  i += 1
49653
49661
  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
49662
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
49663
+ if not ocroom:
49664
+ continue
49660
49665
  _, teachers, students = _getCourseAliasesMembers(croom, ocroom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count)
49661
49666
  if showItemCountOnly:
49662
49667
  if courseShowProperties['members'] != 'students':
@@ -51209,6 +51214,461 @@ def printShowClassroomProfile(users):
51209
51214
  if csvPF:
51210
51215
  csvPF.writeCSVfile('Classroom User Profiles')
51211
51216
 
51217
+ # gam create course-studentgroups
51218
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51219
+ # title <String>
51220
+ # [csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]]
51221
+ def doCreateCourseStudentGroups():
51222
+ croom = buildGAPIObject(API.CLASSROOM)
51223
+ csvPF = None
51224
+ FJQC = FormatJSONQuoteChar(None)
51225
+ courseSelectionParameters = _initCourseSelectionParameters()
51226
+ courseShowProperties = _initCourseShowProperties(['name'])
51227
+ useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
51228
+ kwargs = {'courseId': None, 'body': {}}
51229
+ while Cmd.ArgumentsRemaining():
51230
+ myarg = getArgument()
51231
+ if myarg == 'title':
51232
+ kwargs['body']['title'] = getString(Cmd.OB_STRING)
51233
+ elif _getCourseSelectionParameters(myarg, courseSelectionParameters):
51234
+ pass
51235
+ elif myarg == 'csv':
51236
+ csvPF = CSVPrintFile(['courseId', 'courseName', 'studentGroupId', 'studentGroupTitle'])
51237
+ FJQC = FormatJSONQuoteChar(csvPF)
51238
+ elif csvPF and myarg == 'todrive':
51239
+ csvPF.GetTodriveParameters()
51240
+ else:
51241
+ FJQC.GetFormatJSONQuoteChar(myarg, True)
51242
+ if 'title' not in kwargs['body']:
51243
+ missingArgumentExit('title')
51244
+ if csvPF and FJQC.formatJSON:
51245
+ csvPF.SetJSONTitles(['courseId', 'courseName', 'JSON'])
51246
+ coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
51247
+ if coursesInfo is None:
51248
+ return
51249
+ count = len(coursesInfo)
51250
+ i = 0
51251
+ for course in coursesInfo:
51252
+ i += 1
51253
+ courseId = course['id']
51254
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
51255
+ if not ocroom:
51256
+ continue
51257
+ kwargs['courseId'] = courseId
51258
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, None]
51259
+ try:
51260
+ studentGroup = callGAPI(ocroom.courses().studentGroups(), 'create',
51261
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51262
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51263
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51264
+ previewVersion='V1_20250630_PREVIEW',
51265
+ **kwargs)
51266
+ kvList[-1] = f"{studentGroup['title']}({studentGroup['id']})"
51267
+ if not csvPF:
51268
+ entityActionPerformed(kvList, i, count)
51269
+ elif not FJQC.formatJSON:
51270
+ csvPF.WriteRow({'courseId': courseId, 'courseName': course['name'],
51271
+ 'studentGroupId': studentGroup['id'], 'studentGroupTitle': studentGroup['title']})
51272
+ else:
51273
+ csvPF.WriteRowNoFilter({'courseId': courseId, 'courseName': course['name'],
51274
+ 'JSON': json.dumps(cleanJSON(studentGroup), ensure_ascii=False, sort_keys=True)})
51275
+ except GAPI.notFound as e:
51276
+ entityActionFailedWarning(kvList, str(e), i, count)
51277
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51278
+ entityActionFailedExit([Ent.COURSE, courseId], str(e), i, count)
51279
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51280
+ ClientAPIAccessDeniedExit(str(e))
51281
+ if csvPF:
51282
+ csvPF.writeCSVfile('Course Student Groups')
51283
+
51284
+ # gam update course-studentgroup <CourseID> <StudentGroupID> title <String>
51285
+ def doUpdateCourseStudentGroups():
51286
+ croom = buildGAPIObject(API.CLASSROOM)
51287
+ courseId = getString(Cmd.OB_COURSE_ID)
51288
+ studentGroupId = getString(Cmd.OB_STUDENTGROUP_ID)
51289
+ kwargs = {'courseId': courseId, 'id': studentGroupId, 'body': {}, 'updateMask': ''}
51290
+ while Cmd.ArgumentsRemaining():
51291
+ myarg = getArgument()
51292
+ if myarg == 'title':
51293
+ kwargs['body']['title'] = getString(Cmd.OB_STRING)
51294
+ kwargs['updateMask'] = myarg
51295
+ else:
51296
+ unknownArgumentExit()
51297
+ if 'title' not in kwargs['body']:
51298
+ missingArgumentExit('title')
51299
+ _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51300
+ if count == 0:
51301
+ return
51302
+ ocroom = coursesInfo[courseId]['croom']
51303
+ courseId = coursesInfo[courseId]['id']
51304
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId]
51305
+ try:
51306
+ studentGroup = callGAPI(ocroom.courses().studentGroups(), 'patch',
51307
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51308
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51309
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51310
+ previewVersion='V1_20250630_PREVIEW',
51311
+ **kwargs)
51312
+ kvList[-1] = f"{studentGroup['title']}({studentGroup['id']})"
51313
+ entityActionPerformed(kvList)
51314
+ except GAPI.notFound as e:
51315
+ entityActionFailedWarning(kvList, str(e))
51316
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51317
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51318
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51319
+ ClientAPIAccessDeniedExit(str(e))
51320
+
51321
+ # gam delete course-studentgroups <CourseID> <StudentGroupIDEntity>
51322
+ def doDeleteCourseStudentGroups():
51323
+ croom = buildGAPIObject(API.CLASSROOM)
51324
+ courseId = getString(Cmd.OB_COURSE_ID)
51325
+ studentGroupIds = getEntityList(Cmd.OB_STUDENTGROUP_ID_ENTITY, shlexSplit=False)
51326
+ checkForExtraneousArguments()
51327
+ _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51328
+ if count == 0:
51329
+ return
51330
+ ocroom = coursesInfo[courseId]['croom']
51331
+ courseId = coursesInfo[courseId]['id']
51332
+ kwargs = {'courseId': courseId, 'id': None}
51333
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, None]
51334
+ count = len(studentGroupIds)
51335
+ i = 0
51336
+ for studentGroupId in studentGroupIds:
51337
+ i += 1
51338
+ kwargs['id'] = kvList[-1] = studentGroupId
51339
+ try:
51340
+ callGAPI(ocroom.courses().studentGroups(), 'delete',
51341
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51342
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51343
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51344
+ previewVersion='V1_20250630_PREVIEW',
51345
+ **kwargs)
51346
+ entityActionPerformed(kvList, i, count)
51347
+ except GAPI.notFound as e:
51348
+ entityActionFailedWarning(kvList, str(e), i, count)
51349
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51350
+ entityActionFailedExit([Ent.COURSE, courseId], str(e), i, count)
51351
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51352
+ ClientAPIAccessDeniedExit(str(e))
51353
+
51354
+ # gam clear course-studentgroups
51355
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51356
+ def doClearCourseStudentGroups():
51357
+ croom = buildGAPIObject(API.CLASSROOM)
51358
+ courseSelectionParameters = _initCourseSelectionParameters()
51359
+ courseShowProperties = _initCourseShowProperties(['name'])
51360
+ useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
51361
+ while Cmd.ArgumentsRemaining():
51362
+ myarg = getArgument()
51363
+ if _getCourseSelectionParameters(myarg, courseSelectionParameters):
51364
+ pass
51365
+ else:
51366
+ unknownArgumentExit()
51367
+ coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
51368
+ if coursesInfo is None:
51369
+ return
51370
+ count = len(coursesInfo)
51371
+ i = 0
51372
+ for course in coursesInfo:
51373
+ i += 1
51374
+ courseId = course['id']
51375
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
51376
+ if not ocroom:
51377
+ continue
51378
+ kwargs = {'courseId': courseId, 'id': None}
51379
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, None]
51380
+ studentGroups = []
51381
+ printGettingEntityItemForWhom(Ent.COURSE_STUDENTGROUP, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count)))
51382
+ pageMessage = getPageMessage()
51383
+ try:
51384
+ studentGroups = callGAPIpages(ocroom.courses().studentGroups(), 'list', 'studentGroups',
51385
+ pageMessage=pageMessage,
51386
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51387
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51388
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51389
+ previewVersion='V1_20250630_PREVIEW',
51390
+ courseId=courseId)
51391
+ except GAPI.notFound as e:
51392
+ entityActionFailedWarning([Ent.COURSE, courseId], str(e))
51393
+ continue
51394
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51395
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51396
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51397
+ ClientAPIAccessDeniedExit(str(e))
51398
+ jcount = len(studentGroups)
51399
+ entityPerformActionNumItems([Ent.COURSE, courseId], jcount, Ent.COURSE_STUDENTGROUP, i, count)
51400
+ Ind.Increment()
51401
+ j = 0
51402
+ for studentGroup in studentGroups:
51403
+ j += 1
51404
+ kwargs['id'] = kvList[-1] = studentGroup['id']
51405
+ try:
51406
+ callGAPI(ocroom.courses().studentGroups(), 'delete',
51407
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51408
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51409
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51410
+ previewVersion='V1_20250630_PREVIEW',
51411
+ **kwargs)
51412
+ entityActionPerformed(kvList, j, jcount)
51413
+ except GAPI.notFound as e:
51414
+ entityActionFailedWarning(kvList, str(e), j, jcount)
51415
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51416
+ entityActionFailedExit([Ent.COURSE, courseId], str(e), j, jcount)
51417
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51418
+ ClientAPIAccessDeniedExit(str(e))
51419
+ Ind.Decrement()
51420
+
51421
+ # gam print course-studentgroups [todrive <ToDriveAttribute>*]
51422
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51423
+ # [showitemcountonly] [formatjson [quotechar <Character>]]
51424
+ def doPrintCourseStudentGroups(showMembers=False):
51425
+ croom = buildGAPIObject(API.CLASSROOM)
51426
+ cd = buildGAPIObject(API.DIRECTORY)
51427
+ csvPF = CSVPrintFile(['courseId', 'courseName', 'studentGroupId', 'studentGroupTitle'])
51428
+ FJQC = FormatJSONQuoteChar(csvPF)
51429
+ courseSelectionParameters = _initCourseSelectionParameters()
51430
+ courseShowProperties = _initCourseShowProperties(['name'])
51431
+ showItemCountOnly = False
51432
+ useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
51433
+ while Cmd.ArgumentsRemaining():
51434
+ myarg = getArgument()
51435
+ if myarg == 'todrive':
51436
+ csvPF.GetTodriveParameters()
51437
+ elif _getCourseSelectionParameters(myarg, courseSelectionParameters):
51438
+ pass
51439
+ elif myarg == 'showitemcountonly':
51440
+ showItemCountOnly = True
51441
+ else:
51442
+ FJQC.GetFormatJSONQuoteChar(myarg, True)
51443
+ coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
51444
+ if coursesInfo is None:
51445
+ if showItemCountOnly:
51446
+ writeStdout('0\n')
51447
+ return
51448
+ itemCount = 0
51449
+ count = len(coursesInfo)
51450
+ i = 0
51451
+ for course in coursesInfo:
51452
+ i += 1
51453
+ courseId = course['id']
51454
+ ocroom = _getCourseOwnerSA(croom, course, useOwnerAccess)
51455
+ if not ocroom:
51456
+ continue
51457
+ studentGroups = []
51458
+ printGettingEntityItemForWhom(Ent.COURSE_STUDENTGROUP, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count)))
51459
+ pageMessage = getPageMessage()
51460
+ try:
51461
+ studentGroups = callGAPIpages(ocroom.courses().studentGroups(), 'list', 'studentGroups',
51462
+ pageMessage=pageMessage,
51463
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51464
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51465
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51466
+ previewVersion='V1_20250630_PREVIEW',
51467
+ courseId=courseId)
51468
+ except GAPI.notFound as e:
51469
+ entityActionFailedWarning([Ent.COURSE, courseId], str(e))
51470
+ continue
51471
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51472
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51473
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51474
+ ClientAPIAccessDeniedExit(str(e))
51475
+ if not showMembers and showItemCountOnly:
51476
+ itemCount += len(studentGroups)
51477
+ continue
51478
+ for studentGroup in studentGroups:
51479
+ studentGroupId = studentGroup['id']
51480
+ if not showMembers:
51481
+ if not FJQC.formatJSON:
51482
+ csvPF.WriteRowTitles({'courseId': courseId, 'courseName': course['name'],
51483
+ 'studentGroupId': studentGroupId, 'studentGroupTitle': studentGroup['title']})
51484
+ else:
51485
+ row = {'courseId': courseId, 'courseName': course['name']}
51486
+ if csvPF.CheckRowTitles(row):
51487
+ row['JSON'] = json.dumps(cleanJSON(studentGroup), ensure_ascii=False, sort_keys=False)
51488
+ csvPF.WriteRowTitles(row)
51489
+ continue
51490
+ printGettingEntityItemForWhom(Ent.USER, formatKeyValueList('', [Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId],
51491
+ currentCount(i, count)))
51492
+ pageMessage = getPageMessage()
51493
+ try:
51494
+ students = callGAPIpages(ocroom.courses().studentGroups().studentGroupMembers(), 'list', 'studentGroupMembers',
51495
+ pageMessage=pageMessage,
51496
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
51497
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51498
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51499
+ previewVersion='V1_20250630_PREVIEW',
51500
+ courseId=courseId, studentGroupId=studentGroupId)
51501
+ if showItemCountOnly:
51502
+ itemCount += len(students)
51503
+ continue
51504
+ for member in students:
51505
+ member['userEmail'] = convertUIDtoEmailAddress(f"id:{member['userId']}", cd=cd, emailTypes=['user'])
51506
+ except GAPI.notFound as e:
51507
+ entityActionFailedWarning([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51508
+ continue
51509
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51510
+ entityActionFailedExit([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51511
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51512
+ ClientAPIAccessDeniedExit(str(e))
51513
+ if not FJQC.formatJSON:
51514
+ row = {'courseId': courseId, 'courseName': course['name'],
51515
+ 'studentGroupId': studentGroupId, 'studentGroupTitle': studentGroup['title']}
51516
+ for member in students:
51517
+ csvPF.WriteRowTitles(flattenJSON(member, flattened=row.copy()))
51518
+ else:
51519
+ row = {'courseId': courseId, 'courseName': course['name'],
51520
+ 'studentGroupId': studentGroupId, 'studentGroupTitle': studentGroup['title']}
51521
+ row['JSON'] = json.dumps(list(students))
51522
+ csvPF.WriteRowNoFilter(row)
51523
+ if showItemCountOnly:
51524
+ writeStdout(f'{itemCount}\n')
51525
+ return
51526
+ if not showMembers:
51527
+ title = 'Course Student Groups'
51528
+ else:
51529
+ title = 'Course Student Group Members'
51530
+ csvPF.writeCSVfile(title)
51531
+
51532
+ # gam create course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51533
+ # gam delete course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51534
+ # gam sync course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51535
+ # gam clear course-studentgroup-members <CourseID> <StudentGroupID>
51536
+ def doProcessCourseStudentGroupMembers():
51537
+ def _getCurrentStudents():
51538
+ printGettingEntityItemForWhom(Ent.USER, Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId)
51539
+ pageMessage = getPageMessage()
51540
+ try:
51541
+ return callGAPIpages(ocroom.courses().studentGroups().studentGroupMembers(), 'list', 'studentGroupMembers',
51542
+ pageMessage=pageMessage,
51543
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
51544
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51545
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51546
+ previewVersion='V1_20250630_PREVIEW',
51547
+ courseId=courseId, studentGroupId=studentGroupId)
51548
+ except GAPI.notFound as e:
51549
+ entityActionFailedWarning([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51550
+ return []
51551
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51552
+ entityActionFailedExit([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51553
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51554
+ ClientAPIAccessDeniedExit(str(e))
51555
+
51556
+ def _getStudentUserId(kvList, student, i, count):
51557
+ normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(student)
51558
+ if normalizedEmailAddressOrUID.find('@') == -1:
51559
+ return normalizedEmailAddressOrUID
51560
+ try:
51561
+ return callGAPI(cd.users(), 'get',
51562
+ throwReasons=GAPI.USER_GET_THROW_REASONS,
51563
+ userKey=normalizedEmailAddressOrUID, fields='id')['id']
51564
+ except (GAPI.userNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
51565
+ GAPI.badRequest, GAPI.backendError, GAPI.systemError) as e:
51566
+ entityActionFailedWarning(kvList, str(e), i, count)
51567
+ return None
51568
+
51569
+ def _processStudent(function, kvList, kwargs, i, count):
51570
+ try:
51571
+ callGAPI(ocroom.courses().studentGroups().studentGroupMembers(), function,
51572
+ throwReasons=[GAPI.NOT_FOUND, GAPI.ALREADY_EXISTS,
51573
+ GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
51574
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51575
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51576
+ previewVersion='V1_20250630_PREVIEW',
51577
+ **kwargs)
51578
+ entityActionPerformed(kvList, i, count)
51579
+ except (GAPI.alreadyExists, GAPI.notFound) as e:
51580
+ entityActionFailedWarning(kvList, str(e), i, count)
51581
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51582
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51583
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51584
+ ClientAPIAccessDeniedExit(str(e))
51585
+
51586
+ def _addStudents(students, getUserIds):
51587
+ count = len(students)
51588
+ i = 0
51589
+ entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.USER)
51590
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51591
+ kwargs = {'courseId': courseId, 'studentGroupId': studentGroupId, 'body': {'userId': ''}}
51592
+ for student in students:
51593
+ i += 1
51594
+ if getUserIds:
51595
+ userId = _getStudentUserId(kvList, student, i, count)
51596
+ if userId is None:
51597
+ continue
51598
+ kvList[-1] = student
51599
+ else:
51600
+ userId = student
51601
+ kvList[-1] = convertUIDtoEmailAddress(f"id:{userId}", cd=cd, emailTypes=['user'])
51602
+ kwargs['body']['userId'] = userId
51603
+ _processStudent('create', kvList, kwargs, i, count)
51604
+
51605
+ def _removeStudents(students, getUserIds):
51606
+ count = len(students)
51607
+ i = 0
51608
+ entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.USER)
51609
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51610
+ kwargs = {'courseId': courseId, 'studentGroupId': studentGroupId, 'userId': ''}
51611
+ for student in students:
51612
+ i += 1
51613
+ if getUserIds:
51614
+ userId = _getStudentUserId(kvList, student, i, count)
51615
+ if userId is None:
51616
+ continue
51617
+ kvList[-1] = student
51618
+ else:
51619
+ userId = student
51620
+ kvList[-1] = convertUIDtoEmailAddress(f"id:{userId}", cd=cd, emailTypes=['user'])
51621
+ kwargs['userId'] = userId
51622
+ _processStudent('delete', kvList, kwargs, i, count)
51623
+
51624
+ croom = buildGAPIObject(API.CLASSROOM)
51625
+ cd = buildGAPIObject(API.DIRECTORY)
51626
+ action = Act.Get()
51627
+ courseId = getString(Cmd.OB_COURSE_ID)
51628
+ studentGroupId = getString(Cmd.OB_STUDENTGROUP_ID)
51629
+ if action != Act.CLEAR:
51630
+ _, clStudents = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS, groupMemberType=Ent.TYPE_USER)
51631
+ clStudents = [normalizeEmailAddressOrUID(student) for student in clStudents]
51632
+ checkForExtraneousArguments()
51633
+ _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51634
+ if count == 0:
51635
+ return
51636
+ ocroom = coursesInfo[courseId]['croom']
51637
+ courseId = coursesInfo[courseId]['id']
51638
+ if action in {Act.SYNC, Act.CLEAR}:
51639
+ currentStudents = [student['userId'] for student in _getCurrentStudents()]
51640
+ if action == Act.CLEAR:
51641
+ _removeStudents(currentStudents, False)
51642
+ elif action == Act.DELETE:
51643
+ _removeStudents(clStudents, True)
51644
+ elif action in {Act.ADD, Act.CREATE}:
51645
+ _addStudents(clStudents, True)
51646
+ else: # elif action == Act.SYNC:
51647
+ currentMembersSet = set(currentStudents)
51648
+ syncMembersSet = set()
51649
+ count = len(clStudents)
51650
+ i = 0
51651
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51652
+ for student in clStudents:
51653
+ i += 1
51654
+ kvList[-1] = student
51655
+ userId = _getStudentUserId(kvList, student, i, count)
51656
+ if userId is None:
51657
+ continue
51658
+ syncMembersSet.add(userId)
51659
+ removeStudentsSet = currentMembersSet-syncMembersSet
51660
+ addStudentsSet = syncMembersSet-currentMembersSet
51661
+ Act.Set(Act.DELETE)
51662
+ _removeStudents(removeStudentsSet, False)
51663
+ Act.Set(Act.ADD)
51664
+ _addStudents(addStudentsSet, False)
51665
+
51666
+ # gam print course-studentgroup-members [todrive <ToDriveAttribute>*]
51667
+ # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51668
+ # [showitemcountonly] [formatjson [quotechar <Character>]]
51669
+ def doPrintCourseStudentGroupMembers():
51670
+ doPrintCourseStudentGroups(showMembers=True)
51671
+
51212
51672
  def _showASPs(user, asps, i=0, count=0):
51213
51673
  Act.Set(Act.SHOW)
51214
51674
  jcount = len(asps)
@@ -77337,6 +77797,8 @@ MAIN_ADD_CREATE_FUNCTIONS = {
77337
77797
  Cmd.ARG_CIGROUP: doCreateCIGroup,
77338
77798
  Cmd.ARG_CONTACT: doCreateDomainContact,
77339
77799
  Cmd.ARG_COURSE: doCreateCourse,
77800
+ Cmd.ARG_COURSESTUDENTGROUP: doCreateCourseStudentGroups,
77801
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77340
77802
  Cmd.ARG_DATATRANSFER: doCreateDataTransfer,
77341
77803
  Cmd.ARG_DEVICE: doCreateCIDevice,
77342
77804
  Cmd.ARG_DOMAIN: doCreateDomain,
@@ -77412,6 +77874,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77412
77874
  (Act.CLEAR,
77413
77875
  {Cmd.ARG_ALERTSETTINGS: doClearAlertSettings,
77414
77876
  Cmd.ARG_CONTACT: doClearDomainContacts,
77877
+ Cmd.ARG_COURSESTUDENTGROUP: doClearCourseStudentGroups,
77878
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77415
77879
  }
77416
77880
  ),
77417
77881
  'close':
@@ -77454,6 +77918,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77454
77918
  Cmd.ARG_CONTACTPHOTO: doDeleteDomainContactPhoto,
77455
77919
  Cmd.ARG_COURSE: doDeleteCourse,
77456
77920
  Cmd.ARG_COURSES: doDeleteCourses,
77921
+ Cmd.ARG_COURSESTUDENTGROUP: doDeleteCourseStudentGroups,
77922
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77457
77923
  Cmd.ARG_DEVICE: doDeleteCIDevice,
77458
77924
  Cmd.ARG_DEVICEUSER: doDeleteCIDeviceUser,
77459
77925
  Cmd.ARG_DOMAIN: doDeleteDomain,
@@ -77631,6 +78097,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77631
78097
  Cmd.ARG_COURSEANNOUNCEMENTS: doPrintCourseAnnouncements,
77632
78098
  Cmd.ARG_COURSEMATERIALS: doPrintCourseMaterials,
77633
78099
  Cmd.ARG_COURSEPARTICIPANTS: doPrintCourseParticipants,
78100
+ Cmd.ARG_COURSESTUDENTGROUP: doPrintCourseStudentGroups,
78101
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doPrintCourseStudentGroupMembers,
77634
78102
  Cmd.ARG_COURSESUBMISSIONS: doPrintCourseSubmissions,
77635
78103
  Cmd.ARG_COURSETOPICS: doPrintCourseTopics,
77636
78104
  Cmd.ARG_COURSEWORK: doPrintCourseWork,
@@ -77815,6 +78283,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77815
78283
  (Act.SYNC,
77816
78284
  {Cmd.ARG_DEVICE: doSyncCIDevices,
77817
78285
  Cmd.ARG_SHAREDDRIVEACLS: copySyncSharedDriveACLs,
78286
+ Cmd.ARG_COURSESTUDENTGROUPMEMBERS: doProcessCourseStudentGroupMembers,
77818
78287
  }
77819
78288
  ),
77820
78289
  'unhide':
@@ -77837,6 +78306,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
77837
78306
  Cmd.ARG_CONTACTPHOTO: doUpdateDomainContactPhoto,
77838
78307
  Cmd.ARG_COURSE: doUpdateCourse,
77839
78308
  Cmd.ARG_COURSES: doUpdateCourses,
78309
+ Cmd.ARG_COURSESTUDENTGROUP: doUpdateCourseStudentGroups,
77840
78310
  Cmd.ARG_CROS: doUpdateCrOSDevices,
77841
78311
  Cmd.ARG_CUSTOMER: doUpdateCustomer,
77842
78312
  Cmd.ARG_DEVICE: doUpdateCIDevice,
@@ -77954,6 +78424,7 @@ MAIN_COMMANDS_OBJ_ALIASES = {
77954
78424
  Cmd.ARG_CLASSROOMINVITATIONS: Cmd.ARG_CLASSROOMINVITATION,
77955
78425
  Cmd.ARG_CONTACTS: Cmd.ARG_CONTACT,
77956
78426
  Cmd.ARG_CONTACTPHOTOS: Cmd.ARG_CONTACTPHOTO,
78427
+ Cmd.ARG_COURSESTUDENTGROUPS: Cmd.ARG_COURSESTUDENTGROUP,
77957
78428
  Cmd.ARG_CROSES: Cmd.ARG_CROS,
77958
78429
  Cmd.ARG_DATATRANSFERS: Cmd.ARG_DATATRANSFER,
77959
78430
  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.2
3
+ Version: 7.20.0
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=MQyogAc1kcvEm4gzWDK_GEN64_3XRZOZeFjZAL1u1v8,3584309
1
+ gam/__init__.py,sha256=A7MFQZo7V4ATMe6INRM3Lkc_ci1la6zTZc8xL6GSbtk,3607451
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.2.dist-info/METADATA,sha256=VJhM8oYsDk4w9k8vFvfVqx9DKwLqXIUuXfXXj20nMj8,2940
69
- gam7-7.19.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
- gam7-7.19.2.dist-info/entry_points.txt,sha256=HVUM5J7dA8YwvJfG30jiLefR19ExMs387TWugWd9sf4,42
71
- gam7-7.19.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
- gam7-7.19.2.dist-info/RECORD,,
68
+ gam7-7.20.0.dist-info/METADATA,sha256=muNbkdkJZBuXH9QFD5Q_BGC8FQETAtB2kH6QwP5RPDc,2940
69
+ gam7-7.20.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
+ gam7-7.20.0.dist-info/entry_points.txt,sha256=HVUM5J7dA8YwvJfG30jiLefR19ExMs387TWugWd9sf4,42
71
+ gam7-7.20.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
+ gam7-7.20.0.dist-info/RECORD,,
File without changes