gam7 7.20.2__py3-none-any.whl → 7.20.4__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.20.02'
28
+ __version__ = '7.20.04'
29
29
  __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
30
30
 
31
31
  #pylint: disable=wrong-import-position
@@ -120,6 +120,10 @@ from google.auth.jwt import Credentials as JWTCredentials
120
120
  import google.oauth2.service_account
121
121
  import google_auth_oauthlib.flow
122
122
  import google_auth_httplib2
123
+ import googleapiclient
124
+ import googleapiclient.discovery
125
+ import googleapiclient.errors
126
+ import googleapiclient.http
123
127
  import httplib2
124
128
 
125
129
  httplib2.RETRIES = 5
@@ -139,6 +143,7 @@ from gamlib import glgapi as GAPI
139
143
  from gamlib import glgdata as GDATA
140
144
  from gamlib import glglobals as GM
141
145
  from gamlib import glindent
146
+ from gamlib import gliso8601 as iso8601
142
147
  from gamlib import glmsgs as Msg
143
148
  from gamlib import glskus as SKU
144
149
  from gamlib import gluprop as UProp
@@ -149,12 +154,6 @@ import gdata.apps.audit
149
154
  import gdata.apps.audit.service
150
155
  import gdata.apps.contacts
151
156
  import gdata.apps.contacts.service
152
- # Import local library, does not include discovery documents
153
- import googleapiclient
154
- import googleapiclient.discovery
155
- import googleapiclient.errors
156
- import googleapiclient.http
157
- from iso8601 import iso8601
158
157
 
159
158
  IS08601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S%:z'
160
159
  RFC2822_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S %z'
@@ -2013,11 +2012,11 @@ def getTimeOrDeltaFromNow(returnDateTime=False):
2013
2012
  except OverflowError:
2014
2013
  pass
2015
2014
  try:
2016
- fullDateTime, tz = iso8601.parse_date(argstr)
2015
+ fullDateTime = iso8601.parse_date(argstr)
2017
2016
  Cmd.Advance()
2018
2017
  if not returnDateTime:
2019
2018
  return argstr.replace(' ', 'T')
2020
- return (fullDateTime, tz, argstr.replace(' ', 'T'))
2019
+ return (fullDateTime, fullDateTime.tzinfo, argstr.replace(' ', 'T'))
2021
2020
  except (iso8601.ParseError, OverflowError):
2022
2021
  pass
2023
2022
  invalidArgumentExit(YYYYMMDDTHHMMSS_FORMAT_REQUIRED)
@@ -2335,7 +2334,7 @@ def formatLocalTime(dateTimeStr):
2335
2334
  if dateTimeStr in {NEVER_TIME, NEVER_TIME_NOMS}:
2336
2335
  return GC.Values[GC.NEVER_TIME]
2337
2336
  try:
2338
- timestamp, _ = iso8601.parse_date(dateTimeStr)
2337
+ timestamp = iso8601.parse_date(dateTimeStr)
2339
2338
  if not GC.Values[GC.OUTPUT_TIMEFORMAT]:
2340
2339
  if GM.Globals[GM.CONVERT_TO_LOCAL_TIME]:
2341
2340
  return ISOformatTimeStamp(timestamp.astimezone(GC.Values[GC.TIMEZONE]))
@@ -3736,10 +3735,7 @@ def SetGlobalVariables():
3736
3735
  if value and not os.path.isabs(value):
3737
3736
  value = os.path.expanduser(os.path.join(_getCfgDirectory(sectionName, GC.CONFIG_DIR), value))
3738
3737
  elif not value and itemName == GC.CACERTS_PEM:
3739
- if hasattr(sys, '_MEIPASS'):
3740
- value = os.path.join(sys._MEIPASS, GC.FN_CACERTS_PEM) #pylint: disable=no-member
3741
- else:
3742
- value = os.path.join(GM.Globals[GM.GAM_PATH], GC.FN_CACERTS_PEM)
3738
+ value = os.path.join(GM.Globals[GM.GAM_PATH], GC.FN_CACERTS_PEM)
3743
3739
  return value
3744
3740
 
3745
3741
  def _readGamCfgFile(config, fileName):
@@ -7436,7 +7432,7 @@ def RowFilterMatch(row, titlesList, rowFilter, rowFilterModeAll, rowDropFilter,
7436
7432
  return None
7437
7433
  else:
7438
7434
  try:
7439
- rowTime, _ = iso8601.parse_date(rowDate)
7435
+ rowTime = iso8601.parse_date(rowDate)
7440
7436
  except (iso8601.ParseError, OverflowError):
7441
7437
  return None
7442
7438
  return ISOformatTimeStamp(datetime.datetime(rowTime.year, rowTime.month, rowTime.day, tzinfo=iso8601.UTC))
@@ -7501,7 +7497,7 @@ def RowFilterMatch(row, titlesList, rowFilter, rowFilterModeAll, rowDropFilter,
7501
7497
  if YYYYMMDD_PATTERN.match(rowDate):
7502
7498
  return None
7503
7499
  try:
7504
- rowTime, _ = iso8601.parse_date(rowDate)
7500
+ rowTime = iso8601.parse_date(rowDate)
7505
7501
  except (iso8601.ParseError, OverflowError):
7506
7502
  return None
7507
7503
  return f'{rowTime.hour:02d}:{rowTime.minute:02d}'
@@ -12413,7 +12409,7 @@ def checkServiceAccount(users):
12413
12409
  throwReasons=[GAPI.BAD_REQUEST, GAPI.INVALID, GAPI.NOT_FOUND,
12414
12410
  GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE],
12415
12411
  name=name, fields='validAfterTime')
12416
- key_created, _ = iso8601.parse_date(key['validAfterTime'])
12412
+ key_created = iso8601.parse_date(key['validAfterTime'])
12417
12413
  key_age = todaysTime()-key_created
12418
12414
  printPassFail(Msg.SERVICE_ACCOUNT_PRIVATE_KEY_AGE.format(key_age.days), testWarn if key_age.days > 30 else testPass)
12419
12415
  except GAPI.permissionDenied:
@@ -14368,7 +14364,7 @@ def doReport():
14368
14364
  eventTime = activity.get('id', {}).get('time', UNKNOWN)
14369
14365
  if eventTime != UNKNOWN:
14370
14366
  try:
14371
- eventTime, _ = iso8601.parse_date(eventTime)
14367
+ eventTime = iso8601.parse_date(eventTime)
14372
14368
  except (iso8601.ParseError, OverflowError):
14373
14369
  eventTime = UNKNOWN
14374
14370
  if eventTime != UNKNOWN:
@@ -23867,7 +23863,7 @@ def _filterDeviceFiles(cros, selected, listLimit, startTime, endTime):
23867
23863
  filteredItems = []
23868
23864
  i = 0
23869
23865
  for item in cros.get('deviceFiles', []):
23870
- timeValue, _ = iso8601.parse_date(item['createTime'])
23866
+ timeValue = iso8601.parse_date(item['createTime'])
23871
23867
  if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)):
23872
23868
  item['createTime'] = formatLocalTime(item['createTime'])
23873
23869
  filteredItems.append(item)
@@ -23884,7 +23880,7 @@ def _filterCPUStatusReports(cros, selected, listLimit, startTime, endTime):
23884
23880
  filteredItems = []
23885
23881
  i = 0
23886
23882
  for item in cros.get('cpuStatusReports', []):
23887
- timeValue, _ = iso8601.parse_date(item['reportTime'])
23883
+ timeValue = iso8601.parse_date(item['reportTime'])
23888
23884
  if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)):
23889
23885
  item['reportTime'] = formatLocalTime(item['reportTime'])
23890
23886
  for tempInfo in item.get('cpuTemperatureInfo', []):
@@ -23905,7 +23901,7 @@ def _filterSystemRamFreeReports(cros, selected, listLimit, startTime, endTime):
23905
23901
  filteredItems = []
23906
23902
  i = 0
23907
23903
  for item in cros.get('systemRamFreeReports', []):
23908
- timeValue, _ = iso8601.parse_date(item['reportTime'])
23904
+ timeValue = iso8601.parse_date(item['reportTime'])
23909
23905
  if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)):
23910
23906
  item['reportTime'] = formatLocalTime(item['reportTime'])
23911
23907
  item['systemRamFreeInfo'] = ','.join([str(x) for x in item['systemRamFreeInfo']])
@@ -23938,7 +23934,7 @@ def _filterScreenshotFiles(cros, selected, listLimit, startTime, endTime):
23938
23934
  filteredItems = []
23939
23935
  i = 0
23940
23936
  for item in cros.get('screenshotFiles', []):
23941
- timeValue, _ = iso8601.parse_date(item['createTime'])
23937
+ timeValue = iso8601.parse_date(item['createTime'])
23942
23938
  if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)):
23943
23939
  item['createTime'] = formatLocalTime(item['createTime'])
23944
23940
  filteredItems.append(item)
@@ -24386,7 +24382,7 @@ def getDeviceFilesEntity():
24386
24382
  else:
24387
24383
  for timeItem in myarg.split(','):
24388
24384
  try:
24389
- timestamp, _ = iso8601.parse_date(timeItem)
24385
+ timestamp = iso8601.parse_date(timeItem)
24390
24386
  deviceFilesEntity['list'].append(ISOformatTimeStamp(timestamp.astimezone(GC.Values[GC.TIMEZONE])))
24391
24387
  except (iso8601.ParseError, OverflowError):
24392
24388
  Cmd.Backup()
@@ -24415,14 +24411,14 @@ def _selectDeviceFiles(deviceId, deviceFiles, deviceFilesEntity):
24415
24411
  count = 0
24416
24412
  if deviceFilesEntity['time'][0] == 'before':
24417
24413
  for deviceFile in deviceFiles:
24418
- createTime, _ = iso8601.parse_date(deviceFile['createTime'])
24414
+ createTime = iso8601.parse_date(deviceFile['createTime'])
24419
24415
  if createTime >= dateTime:
24420
24416
  break
24421
24417
  count += 1
24422
24418
  return deviceFiles[:count]
24423
24419
  # if deviceFilesEntity['time'][0] == 'after':
24424
24420
  for deviceFile in deviceFiles:
24425
- createTime, _ = iso8601.parse_date(deviceFile['createTime'])
24421
+ createTime = iso8601.parse_date(deviceFile['createTime'])
24426
24422
  if createTime >= dateTime:
24427
24423
  break
24428
24424
  count += 1
@@ -24431,14 +24427,14 @@ def _selectDeviceFiles(deviceId, deviceFiles, deviceFilesEntity):
24431
24427
  dateTime = deviceFilesEntity['range'][1]
24432
24428
  spos = 0
24433
24429
  for deviceFile in deviceFiles:
24434
- createTime, _ = iso8601.parse_date(deviceFile['createTime'])
24430
+ createTime = iso8601.parse_date(deviceFile['createTime'])
24435
24431
  if createTime >= dateTime:
24436
24432
  break
24437
24433
  spos += 1
24438
24434
  dateTime = deviceFilesEntity['range'][2]
24439
24435
  epos = spos
24440
24436
  for deviceFile in deviceFiles[spos:]:
24441
- createTime, _ = iso8601.parse_date(deviceFile['createTime'])
24437
+ createTime = iso8601.parse_date(deviceFile['createTime'])
24442
24438
  if createTime >= dateTime:
24443
24439
  break
24444
24440
  epos += 1
@@ -25221,7 +25217,7 @@ def doInfoPrintShowCrOSTelemetry():
25221
25217
  i = 0
25222
25218
  for item in listItems:
25223
25219
  if 'reportTime' in item:
25224
- timeValue, _ = iso8601.parse_date(item['reportTime'])
25220
+ timeValue = iso8601.parse_date(item['reportTime'])
25225
25221
  else:
25226
25222
  timeValue = None
25227
25223
  if (timeValue is None) or (((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime))):
@@ -40068,7 +40064,7 @@ def _getEventDaysOfWeek(event):
40068
40064
  pass
40069
40065
  elif 'dateTime' in event[attr]:
40070
40066
  try:
40071
- dateTime, _ = iso8601.parse_date(event[attr]['dateTime'])
40067
+ dateTime = iso8601.parse_date(event[attr]['dateTime'])
40072
40068
  event[attr]['dayOfWeek'] = calendarlib.day_abbr[dateTime.weekday()]
40073
40069
  except (iso8601.ParseError, OverflowError):
40074
40070
  pass
@@ -48699,7 +48695,7 @@ def _courseItemPassesFilter(item, courseItemFilter):
48699
48695
  return False
48700
48696
  startTime = courseItemFilter['startTime']
48701
48697
  endTime = courseItemFilter['endTime']
48702
- timeValue, _ = iso8601.parse_date(timeStr)
48698
+ timeValue = iso8601.parse_date(timeStr)
48703
48699
  return ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime))
48704
48700
 
48705
48701
  def _gettingCoursesQuery(courseSelectionParameters):
@@ -51247,7 +51243,7 @@ def doCreateCourseStudentGroups():
51247
51243
  while Cmd.ArgumentsRemaining():
51248
51244
  myarg = getArgument()
51249
51245
  if myarg == 'title':
51250
- titles.append(getString(Cmd.OB_STRING))
51246
+ titles.append(getString(Cmd.OB_STRING, maxLen=100))
51251
51247
  elif myarg == 'select':
51252
51248
  titles.extend(getEntityList(Cmd.OB_STRING_ENTITY, shlexSplit=True))
51253
51249
  elif _getCourseSelectionParameters(myarg, courseSelectionParameters):
@@ -51450,8 +51446,26 @@ def doClearCourseStudentGroups():
51450
51446
  # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
51451
51447
  # [showitemcountonly] [formatjson [quotechar <Character>]]
51452
51448
  def doPrintCourseStudentGroups(showMembers=False):
51449
+ def _getCourseStudents():
51450
+ studentIdEmailMap = {}
51451
+ try:
51452
+ students = callGAPIpages(ocroom.courses().students(), 'list', 'students',
51453
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
51454
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51455
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51456
+ courseId=courseId, fields='nextPageToken,students(profile(id,emailAddress))',
51457
+ pageSize=GC.Values[GC.CLASSROOM_MAX_RESULTS])
51458
+ for student in students:
51459
+ studentIdEmailMap[student['profile']['id']] = student['profile']['emailAddress']
51460
+ except GAPI.notFound:
51461
+ pass
51462
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51463
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51464
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51465
+ ClientAPIAccessDeniedExit(str(e))
51466
+ return studentIdEmailMap
51467
+
51453
51468
  croom = buildGAPIObject(API.CLASSROOM)
51454
- cd = buildGAPIObject(API.DIRECTORY)
51455
51469
  csvPF = CSVPrintFile(['courseId', 'courseName', 'studentGroupId', 'studentGroupTitle'])
51456
51470
  FJQC = FormatJSONQuoteChar(csvPF)
51457
51471
  courseSelectionParameters = _initCourseSelectionParameters()
@@ -51503,6 +51517,8 @@ def doPrintCourseStudentGroups(showMembers=False):
51503
51517
  if not showMembers and showItemCountOnly:
51504
51518
  itemCount += len(studentGroups)
51505
51519
  continue
51520
+ if showMembers:
51521
+ studentIdEmailMap = _getCourseStudents()
51506
51522
  for studentGroup in studentGroups:
51507
51523
  studentGroupId = studentGroup['id']
51508
51524
  if not showMembers:
@@ -51515,7 +51531,7 @@ def doPrintCourseStudentGroups(showMembers=False):
51515
51531
  row['JSON'] = json.dumps(cleanJSON(studentGroup), ensure_ascii=False, sort_keys=False)
51516
51532
  csvPF.WriteRowTitles(row)
51517
51533
  continue
51518
- printGettingEntityItemForWhom(Ent.USER, formatKeyValueList('', [Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId],
51534
+ printGettingEntityItemForWhom(Ent.STUDENT, formatKeyValueList('', [Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId],
51519
51535
  currentCount(i, count)))
51520
51536
  pageMessage = getPageMessage()
51521
51537
  try:
@@ -51530,7 +51546,7 @@ def doPrintCourseStudentGroups(showMembers=False):
51530
51546
  itemCount += len(students)
51531
51547
  continue
51532
51548
  for member in students:
51533
- member['userEmail'] = convertUIDtoEmailAddress(f"id:{member['userId']}", cd=cd, emailTypes=['user'])
51549
+ member['userEmail'] = studentIdEmailMap.get(member['userId'], member['userId'])
51534
51550
  except GAPI.notFound as e:
51535
51551
  entityActionFailedWarning([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
51536
51552
  continue
@@ -51562,8 +51578,29 @@ def doPrintCourseStudentGroups(showMembers=False):
51562
51578
  # gam sync course-studentgroup-members <CourseID> <StudentGroupID> <UserTypeEntity>
51563
51579
  # gam clear course-studentgroup-members <CourseID> <StudentGroupID>
51564
51580
  def doProcessCourseStudentGroupMembers():
51565
- def _getCurrentStudents():
51566
- printGettingEntityItemForWhom(Ent.USER, Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId)
51581
+ def _getCourseStudents():
51582
+ studentIdEmailMap = {}
51583
+ studentEmailIdMap = {}
51584
+ try:
51585
+ students = callGAPIpages(ocroom.courses().students(), 'list', 'students',
51586
+ throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
51587
+ GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
51588
+ retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
51589
+ courseId=courseId, fields='nextPageToken,students(profile(id,emailAddress))',
51590
+ pageSize=GC.Values[GC.CLASSROOM_MAX_RESULTS])
51591
+ for student in students:
51592
+ studentIdEmailMap[student['profile']['id']] = student['profile']['emailAddress'].lower()
51593
+ studentEmailIdMap[student['profile']['emailAddress'].lower()] = student['profile']['id']
51594
+ return (studentIdEmailMap, studentEmailIdMap)
51595
+ except GAPI.notFound as e:
51596
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51597
+ except (GAPI.serviceNotAvailable, GAPI.notImplemented) as e:
51598
+ entityActionFailedExit([Ent.COURSE, courseId], str(e))
51599
+ except (GAPI.forbidden, GAPI.permissionDenied) as e:
51600
+ ClientAPIAccessDeniedExit(str(e))
51601
+
51602
+ def _getGroupCurrentStudents():
51603
+ printGettingEntityItemForWhom(Ent.STUDENT, formatKeyValueList('', [Ent.Singular(Ent.COURSE_STUDENTGROUP), studentGroupId], ''))
51567
51604
  pageMessage = getPageMessage()
51568
51605
  try:
51569
51606
  return callGAPIpages(ocroom.courses().studentGroups().studentGroupMembers(), 'list', 'studentGroupMembers',
@@ -51581,18 +51618,24 @@ def doProcessCourseStudentGroupMembers():
51581
51618
  except (GAPI.forbidden, GAPI.permissionDenied) as e:
51582
51619
  ClientAPIAccessDeniedExit(str(e))
51583
51620
 
51584
- def _getStudentUserId(kvList, student, i, count):
51585
- normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(student)
51586
- if normalizedEmailAddressOrUID.find('@') == -1:
51587
- return normalizedEmailAddressOrUID
51588
- try:
51589
- return callGAPI(cd.users(), 'get',
51590
- throwReasons=GAPI.USER_GET_THROW_REASONS,
51591
- userKey=normalizedEmailAddressOrUID, fields='id')['id']
51592
- except (GAPI.userNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
51593
- GAPI.badRequest, GAPI.backendError, GAPI.systemError) as e:
51594
- entityActionFailedWarning(kvList, str(e), i, count)
51595
- return None
51621
+ def _validateClStudents(clStudents):
51622
+ status = True
51623
+ clStudentIds = []
51624
+ count = len(clStudents)
51625
+ i = 0
51626
+ kvList = [Ent.COURSE, courseId, Ent.STUDENT, '']
51627
+ for student in clStudents:
51628
+ i += 1
51629
+ student = normalizeEmailAddressOrUID(student)
51630
+ if student in studentIdEmailMap:
51631
+ clStudentIds.append(student)
51632
+ elif student in studentEmailIdMap:
51633
+ clStudentIds.append(studentEmailIdMap[student])
51634
+ else:
51635
+ kvList[-1] = student
51636
+ entityActionFailedWarning(kvList, Msg.STUDENT_NOT_IN_COURSE, i, count)
51637
+ status = False
51638
+ return clStudentIds if status else None
51596
51639
 
51597
51640
  def _processStudent(function, kvList, kwargs, i, count):
51598
51641
  try:
@@ -51611,85 +51654,69 @@ def doProcessCourseStudentGroupMembers():
51611
51654
  except (GAPI.forbidden, GAPI.permissionDenied) as e:
51612
51655
  ClientAPIAccessDeniedExit(str(e))
51613
51656
 
51614
- def _addStudents(students, getUserIds):
51657
+ def _addStudents(students):
51615
51658
  count = len(students)
51616
51659
  i = 0
51617
- entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.USER)
51618
- kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51660
+ entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.STUDENT)
51661
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.STUDENT, '']
51619
51662
  kwargs = {'courseId': courseId, 'studentGroupId': studentGroupId, 'body': {'userId': ''}}
51663
+ Ind.Increment()
51620
51664
  for student in students:
51621
51665
  i += 1
51622
- if getUserIds:
51623
- userId = _getStudentUserId(kvList, student, i, count)
51624
- if userId is None:
51625
- continue
51626
- kvList[-1] = student
51627
- else:
51628
- userId = student
51629
- kvList[-1] = convertUIDtoEmailAddress(f"id:{userId}", cd=cd, emailTypes=['user'])
51630
- kwargs['body']['userId'] = userId
51666
+ kvList[-1] = studentIdEmailMap[student]
51667
+ kwargs['body']['userId'] = student
51631
51668
  _processStudent('create', kvList, kwargs, i, count)
51669
+ Ind.Decrement()
51632
51670
 
51633
- def _removeStudents(students, getUserIds):
51671
+ def _removeStudents(students):
51634
51672
  count = len(students)
51635
51673
  i = 0
51636
- entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.USER)
51637
- kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51674
+ entityPerformActionNumItems([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], count, Ent.STUDENT)
51675
+ kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.STUDENT, '']
51638
51676
  kwargs = {'courseId': courseId, 'studentGroupId': studentGroupId, 'userId': ''}
51677
+ Ind.Increment()
51639
51678
  for student in students:
51640
51679
  i += 1
51641
- if getUserIds:
51642
- userId = _getStudentUserId(kvList, student, i, count)
51643
- if userId is None:
51644
- continue
51645
- kvList[-1] = student
51646
- else:
51647
- userId = student
51648
- kvList[-1] = convertUIDtoEmailAddress(f"id:{userId}", cd=cd, emailTypes=['user'])
51649
- kwargs['userId'] = userId
51680
+ kvList[-1] = studentIdEmailMap[student]
51681
+ kwargs['userId'] = student
51650
51682
  _processStudent('delete', kvList, kwargs, i, count)
51683
+ Ind.Decrement()
51651
51684
 
51652
51685
  croom = buildGAPIObject(API.CLASSROOM)
51653
- cd = buildGAPIObject(API.DIRECTORY)
51654
51686
  action = Act.Get()
51655
51687
  courseId = getString(Cmd.OB_COURSE_ID)
51656
51688
  studentGroupId = getString(Cmd.OB_STUDENTGROUP_ID)
51657
51689
  if action != Act.CLEAR:
51658
51690
  _, clStudents = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS, groupMemberType=Ent.TYPE_USER)
51659
- clStudents = [normalizeEmailAddressOrUID(student) for student in clStudents]
51691
+ clStudentIds = []
51660
51692
  checkForExtraneousArguments()
51661
51693
  _, count, coursesInfo = _getCoursesOwnerInfo(croom, [courseId], GC.Values[GC.USE_COURSE_OWNER_ACCESS])
51662
51694
  if count == 0:
51663
51695
  return
51664
51696
  ocroom = coursesInfo[courseId]['croom']
51665
51697
  courseId = coursesInfo[courseId]['id']
51698
+ studentIdEmailMap, studentEmailIdMap = _getCourseStudents()
51666
51699
  if action in {Act.SYNC, Act.CLEAR}:
51667
- currentStudents = [student['userId'] for student in _getCurrentStudents()]
51700
+ currentStudents = [student['userId'] for student in _getGroupCurrentStudents()]
51701
+ if action != Act.CLEAR:
51702
+ clStudentIds = _validateClStudents(clStudents)
51703
+ if clStudentIds is None:
51704
+ return
51668
51705
  if action == Act.CLEAR:
51669
- _removeStudents(currentStudents, False)
51706
+ _removeStudents(currentStudents)
51670
51707
  elif action == Act.DELETE:
51671
- _removeStudents(clStudents, True)
51708
+ _removeStudents(clStudentIds)
51672
51709
  elif action in {Act.ADD, Act.CREATE}:
51673
- _addStudents(clStudents, True)
51710
+ _addStudents(clStudentIds)
51674
51711
  else: # elif action == Act.SYNC:
51675
51712
  currentMembersSet = set(currentStudents)
51676
- syncMembersSet = set()
51677
- count = len(clStudents)
51678
- i = 0
51679
- kvList = [Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId, Ent.USER, '']
51680
- for student in clStudents:
51681
- i += 1
51682
- kvList[-1] = student
51683
- userId = _getStudentUserId(kvList, student, i, count)
51684
- if userId is None:
51685
- continue
51686
- syncMembersSet.add(userId)
51713
+ syncMembersSet = set(clStudentIds)
51687
51714
  removeStudentsSet = currentMembersSet-syncMembersSet
51688
51715
  addStudentsSet = syncMembersSet-currentMembersSet
51689
51716
  Act.Set(Act.DELETE)
51690
- _removeStudents(removeStudentsSet, False)
51717
+ _removeStudents(removeStudentsSet)
51691
51718
  Act.Set(Act.ADD)
51692
- _addStudents(addStudentsSet, False)
51719
+ _addStudents(addStudentsSet)
51693
51720
 
51694
51721
  # gam print course-studentgroup-members [todrive <ToDriveAttribute>*]
51695
51722
  # (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
@@ -56227,7 +56254,7 @@ def _selectRevisionIds(drive, fileId, origUser, user, i, count, j, jcount, revis
56227
56254
  count = 0
56228
56255
  if revisionsEntity['time'][0] == 'before':
56229
56256
  for revision in results:
56230
- modifiedDateTime, _ = iso8601.parse_date(revision['modifiedTime'])
56257
+ modifiedDateTime = iso8601.parse_date(revision['modifiedTime'])
56231
56258
  if modifiedDateTime >= dateTime:
56232
56259
  break
56233
56260
  revisionIds.append(revision['id'])
@@ -56237,7 +56264,7 @@ def _selectRevisionIds(drive, fileId, origUser, user, i, count, j, jcount, revis
56237
56264
  return revisionIds
56238
56265
  # time: after
56239
56266
  for revision in results:
56240
- modifiedDateTime, _ = iso8601.parse_date(revision['modifiedTime'])
56267
+ modifiedDateTime = iso8601.parse_date(revision['modifiedTime'])
56241
56268
  if modifiedDateTime >= dateTime:
56242
56269
  revisionIds.append(revision['id'])
56243
56270
  count += 1
@@ -56249,7 +56276,7 @@ def _selectRevisionIds(drive, fileId, origUser, user, i, count, j, jcount, revis
56249
56276
  endDateTime = revisionsEntity['range'][2]
56250
56277
  count = 0
56251
56278
  for revision in results:
56252
- modifiedDateTime, _ = iso8601.parse_date(revision['modifiedTime'])
56279
+ modifiedDateTime = iso8601.parse_date(revision['modifiedTime'])
56253
56280
  if modifiedDateTime >= startDateTime:
56254
56281
  if modifiedDateTime >= endDateTime:
56255
56282
  break
@@ -56459,7 +56486,7 @@ def _selectRevisionResults(results, fileId, origUser, revisionsEntity, previewDe
56459
56486
  count = 0
56460
56487
  if revisionsEntity['time'][0] == 'before':
56461
56488
  for revision in results:
56462
- modifiedDateTime, _ = iso8601.parse_date(revision['modifiedTime'])
56489
+ modifiedDateTime = iso8601.parse_date(revision['modifiedTime'])
56463
56490
  if modifiedDateTime >= dateTime:
56464
56491
  break
56465
56492
  count += 1
@@ -56470,7 +56497,7 @@ def _selectRevisionResults(results, fileId, origUser, revisionsEntity, previewDe
56470
56497
  return results[:count]
56471
56498
  # time: after
56472
56499
  for revision in results:
56473
- modifiedDateTime, _ = iso8601.parse_date(revision['modifiedTime'])
56500
+ modifiedDateTime = iso8601.parse_date(revision['modifiedTime'])
56474
56501
  if modifiedDateTime >= dateTime:
56475
56502
  break
56476
56503
  count += 1
@@ -56487,7 +56514,7 @@ def _selectRevisionResults(results, fileId, origUser, revisionsEntity, previewDe
56487
56514
  count = 0
56488
56515
  selectedResults = []
56489
56516
  for revision in results:
56490
- modifiedDateTime, _ = iso8601.parse_date(revision['modifiedTime'])
56517
+ modifiedDateTime = iso8601.parse_date(revision['modifiedTime'])
56491
56518
  if modifiedDateTime >= startDateTime:
56492
56519
  if modifiedDateTime >= endDateTime:
56493
56520
  break
@@ -57025,7 +57052,7 @@ class PermissionMatch():
57025
57052
  break
57026
57053
  elif field in {'expirationstart', 'expirationend'}:
57027
57054
  if 'expirationTime' in permission:
57028
- expirationDateTime, _ = iso8601.parse_date(permission['expirationTime'])
57055
+ expirationDateTime = iso8601.parse_date(permission['expirationTime'])
57029
57056
  if field == 'expirationstart':
57030
57057
  if expirationDateTime < value:
57031
57058
  break
@@ -65073,7 +65100,7 @@ def _checkFileIdEntityDomainAccess(fileIdEntity, useDomainAdminAccess):
65073
65100
  # (mappermissionsdomain <DomainName> <DomainName>)*
65074
65101
  # [moveToNewOwnersRoot [<Boolean>]]
65075
65102
  # [updatesheetprotectedranges [<Boolean>]]
65076
- # [sendemail] [emailmessage <String>]
65103
+ # [sendemail|sendnotification] [emailmessage <String>]
65077
65104
  # [showtitles] [nodetails|(csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]])]
65078
65105
  def createDriveFileACL(users, useDomainAdminAccess=False):
65079
65106
  moveToNewOwnersRoot = False
@@ -65117,7 +65144,7 @@ def createDriveFileACL(users, useDomainAdminAccess=False):
65117
65144
  elif myarg in {'expiration', 'expires'}:
65118
65145
  expirationLocation = Cmd.Location()
65119
65146
  body['expirationTime'] = getTimeOrDeltaFromNow()
65120
- elif myarg == 'sendemail':
65147
+ elif myarg in {'sendemail', 'sendnotification'}:
65121
65148
  sendNotificationEmail = True
65122
65149
  elif myarg == 'emailmessage':
65123
65150
  sendNotificationEmail = True
@@ -65373,7 +65400,7 @@ def doUpdateDriveFileACLs():
65373
65400
  updateDriveFileACLs([_getAdminEmail()], True)
65374
65401
 
65375
65402
  # gam [<UserTypeEntity>] create permissions <DriveFileEntity> <DriveFilePermissionsEntity> [asadmin]
65376
- # [expiration <Time>] [sendmail] [emailmessage <String>]
65403
+ # [expiration <Time>] [sendemail|sendnotification] [emailmessage <String>]
65377
65404
  # [moveToNewOwnersRoot [<Boolean>]]
65378
65405
  # <PermissionMatch>* [<PermissionMatchAction>]
65379
65406
  def createDriveFilePermissions(users, useDomainAdminAccess=False):
@@ -65491,7 +65518,7 @@ def createDriveFilePermissions(users, useDomainAdminAccess=False):
65491
65518
  moveToNewOwnersRoot = getBoolean()
65492
65519
  elif myarg in {'expiration', 'expires'}:
65493
65520
  expiration = getTimeOrDeltaFromNow()
65494
- elif myarg == 'sendemail':
65521
+ elif myarg in {'sendemail', 'sendnotification'}:
65495
65522
  sendNotificationEmail = True
65496
65523
  elif myarg == 'emailmessage':
65497
65524
  sendNotificationEmail = True
@@ -148,13 +148,12 @@ def parse_date(datestring):
148
148
  groups = m.groupdict()
149
149
  tz = parse_timezone(groups)
150
150
  try:
151
- return (datetime(year=int(groups['year']),
152
- month=int(groups['month']),
153
- day=int(groups['day']),
154
- hour=int(groups['hour']),
155
- minute=int(groups['minute']),
156
- second=int(groups['second']),
157
- tzinfo=tz),
158
- tz)
151
+ return datetime(year=int(groups['year']),
152
+ month=int(groups['month']),
153
+ day=int(groups['day']),
154
+ hour=int(groups['hour']),
155
+ minute=int(groups['minute']),
156
+ second=int(groups['second']),
157
+ tzinfo=tz)
159
158
  except Exception as e:
160
159
  raise ParseError(e)
gam/gamlib/glmsgs.py CHANGED
@@ -499,6 +499,7 @@ STATISTICS_MOVE_FILE = 'Total: {0}, Moved: {1}, Shortcut created {2}, Shortcut e
499
499
  STATISTICS_MOVE_FOLDER = 'Total: {0}, Moved: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Merged: {5}, Move Failed: {6}, Not writable: {7}'
500
500
  STATISTICS_USER_NOT_ORGANIZER = 'User not organizer: {0}'
501
501
  STRING_LENGTH = 'string length'
502
+ STUDENT_NOT_IN_COURSE = 'Student not in course'
502
503
  SUBKEY_FIELD_MISMATCH = 'subkeyfield {0} does not match saved subkeyfield {1}'
503
504
  SUBSCRIPTION_NOT_FOUND = 'Could not find subscription'
504
505
  SUFFIX_NOT_ALLOWED_WITH_CUSTOMLANGUAGE = 'Suffix {0} not allowed with customLanguage {1}'
gam/gamlib/glskus.py CHANGED
@@ -100,7 +100,7 @@ _SKUS = {
100
100
  '1010470003': {
101
101
  'product': '101047', 'aliases': ['geminibiz'], 'displayName': 'Gemini Business'},
102
102
  '1010470004': {
103
- 'product': '101047', 'aliases': ['geminiedu'], 'displayName': 'Gemini Education'},
103
+ 'product': '101047', 'aliases': ['gaiproedu', 'geminiedu'], 'displayName': 'Google AI Pro for Education'},
104
104
  '1010470005': {
105
105
  'product': '101047', 'aliases': ['geminiedupremium'], 'displayName': 'Gemini Education Premium'},
106
106
  '1010470006': {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gam7
3
- Version: 7.20.2
3
+ Version: 7.20.4
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