From 5a43184700b6cf998a56f927b06039d60fc34afa Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Sat, 4 Jul 2026 19:45:50 -0400 Subject: [PATCH] Move misplaced functions where they belong. easy and trivial. --- src/gam/__init__.py | 8 +- src/gam/cmd/alerts.py | 2 +- src/gam/cmd/analytics.py | 1 - src/gam/cmd/businessprofile.py | 77 +++++++++ src/gam/cmd/calendar.py | 6 +- src/gam/cmd/chromeapps.py | 1 - src/gam/cmd/cigroups/groups.py | 2 +- src/gam/cmd/cigroups/members.py | 3 +- src/gam/cmd/ciuserinvitations.py | 23 --- src/gam/cmd/courses/content.py | 3 +- src/gam/cmd/courses/courses.py | 5 +- src/gam/cmd/courses/guardians.py | 3 +- src/gam/cmd/courses/participants.py | 14 +- src/gam/cmd/cros.py | 10 +- src/gam/cmd/customer.py | 12 +- src/gam/cmd/datatransfer.py | 3 +- src/gam/cmd/domains.py | 4 +- src/gam/cmd/drive/activity.py | 3 +- src/gam/cmd/drive/copymove/copymove_util.py | 3 +- src/gam/cmd/drive/core.py | 13 +- src/gam/cmd/drive/fileinfo.py | 4 +- src/gam/cmd/drive/filelist.py | 4 +- src/gam/cmd/drive/filetree.py | 1 + src/gam/cmd/drive/permissions.py | 7 +- src/gam/cmd/drive/shareddrives.py | 3 +- src/gam/cmd/drive/transfer/fileops.py | 2 +- src/gam/cmd/drive/transfer/ownership.py | 3 +- src/gam/cmd/gmail/filters.py | 2 +- src/gam/cmd/gmail/labels.py | 8 +- src/gam/cmd/gmail/messages.py | 10 +- src/gam/cmd/gmail/signature.py | 2 +- src/gam/cmd/gmail/smime.py | 4 +- src/gam/cmd/groups/groups.py | 5 +- src/gam/cmd/groups/members.py | 8 +- src/gam/cmd/mobile.py | 4 +- src/gam/cmd/notes.py | 2 +- src/gam/cmd/orgunits.py | 4 +- src/gam/cmd/people.py | 3 +- src/gam/cmd/printers.py | 2 - src/gam/cmd/project.py | 7 +- src/gam/cmd/reports.py | 2 +- src/gam/cmd/sites.py | 11 +- src/gam/cmd/sso.py | 37 ++-- src/gam/cmd/tagmanager.py | 177 ++++++++++++++++++++ src/gam/cmd/tasks.py | 145 +--------------- src/gam/cmd/users/display.py | 7 +- src/gam/cmd/users/manage.py | 3 +- src/gam/cmd/userservices.py | 17 +- src/gam/cmd/youtube.py | 148 ++++++++++++++++ src/gam/util/api.py | 41 +---- src/gam/util/args.py | 82 +-------- src/gam/util/batch.py | 20 +++ src/gam/util/connection.py | 43 ++++- src/gam/util/csv_pf.py | 19 +-- src/gam/util/entity.py | 22 ++- src/gam/util/output.py | 73 +++++++- tests/test_args.py | 30 ++-- 57 files changed, 698 insertions(+), 460 deletions(-) create mode 100644 src/gam/cmd/businessprofile.py create mode 100644 src/gam/cmd/tagmanager.py create mode 100644 src/gam/cmd/youtube.py diff --git a/src/gam/__init__.py b/src/gam/__init__.py index f88164a7..335b97e3 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -366,6 +366,7 @@ from gam.cmd.cros import ( ) from gam.cmd.customer import ( doInfoCustomer, + doInfoCustomerId, doInfoDomain, doInfoInstance, doPrintShowDomains, @@ -645,7 +646,6 @@ from gam.cmd.project import ( doDeleteSvcAcctKeys, doEnableAPIs, doInfoCurrentProjectId, - doInfoCustomerId, doInfoGCPOrgId, doPrintShowProjects, doPrintShowSvcAccts, @@ -725,13 +725,15 @@ from gam.cmd.sso import ( doUpdateInboundSSOAssignment, doUpdateInboundSSOProfile, ) -from gam.cmd.tasks import ( - importTasklist, +from gam.cmd.tagmanager import ( printShowTagManagerAccounts, printShowTagManagerContainers, printShowTagManagerPermissions, printShowTagManagerTags, printShowTagManagerWorkspaces, +) +from gam.cmd.tasks import ( + importTasklist, printShowTasklists, printShowTasks, processTasklists, diff --git a/src/gam/cmd/alerts.py b/src/gam/cmd/alerts.py index 59a2fd72..40a19cea 100644 --- a/src/gam/cmd/alerts.py +++ b/src/gam/cmd/alerts.py @@ -2,7 +2,6 @@ import json -from gam.util.args import formatLocalTime from gamlib import api as API from gamlib import gapi as GAPI @@ -10,6 +9,7 @@ from gam.var import Act, Cmd, Ent, Ind from gam.util.api import _getAdminEmail from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages +from gam.util.output import formatLocalTime from gam.util.args import ( OrderBy, checkForExtraneousArguments, diff --git a/src/gam/cmd/analytics.py b/src/gam/cmd/analytics.py index 111c3346..ed28ae00 100644 --- a/src/gam/cmd/analytics.py +++ b/src/gam/cmd/analytics.py @@ -27,7 +27,6 @@ from gam.util.display import ( ) from gam.util.entity import getEntityArgument from gam.util.errors import missingArgumentExit -from gam.cmd.reseller import ANALYTIC_ENTITY_MAP def printShowAnalyticItems(users, entityType): diff --git a/src/gam/cmd/businessprofile.py b/src/gam/cmd/businessprofile.py new file mode 100644 index 00000000..4d567950 --- /dev/null +++ b/src/gam/cmd/businessprofile.py @@ -0,0 +1,77 @@ +"""GAM Business Profile account management.""" + +from gamlib import api as API +from gamlib import gapi as GAPI +from gam.var import Act, Cmd, Ent, Ind +from gam.util.svcacct import buildGAPIServiceObject +from gam.util.api_call import callGAPIpages +from gam.util.args import getArgument, getChoice +from gam.util.csv_pf import CSVPrintFile, flattenJSON, showJSON +from gam.util.display import ( + entityPerformActionNumItems, + getPageMessageForWhom, + printGettingAllEntityItemsForWhom, + printKeyValueListWithCount, +) +from gam.util.entity import getEntityArgument +from gam.util.errors import unknownArgumentExit +from gam.util.access import accessErrorExitNonDirectory + +PROFILE_ACCOUNT_TYPE_MAP = { + 'locationgroup': 'LOCATION_GROUP', + 'organization': 'ORGANIZATION', + 'personal': 'PERSONAL', + 'usergroup': 'USER_GROUP', + } + +# gam show businessprofileaccounts +# [type locationgroup|organization|personal|usergroup] +# gam print businessprofileaccounts [todrive *] +# [type locationgroup|organization|personal|usergroup] +def printShowBusinessProfileAccounts(users): + csvPF = CSVPrintFile(['User', 'name', 'accountName']) if Act.csvFormat() else None + kwargs = {} + while Cmd.ArgumentsRemaining(): + myarg = getArgument() + if csvPF and myarg == 'todrive': + csvPF.GetTodriveParameters() + elif myarg == 'type': + kwargs['filter'] = f'type={getChoice(PROFILE_ACCOUNT_TYPE_MAP, mapChoice=True)}' + else: + unknownArgumentExit() + i, count, users = getEntityArgument(users) + for user in users: + i += 1 + user, bp = buildGAPIServiceObject(API.BUSINESSACCOUNTMANAGEMENT, user, i, count) + if not bp: + continue + if csvPF: + printGettingAllEntityItemsForWhom(Ent.BUSINESS_PROFILE_ACCOUNT, user, i, count, query=kwargs.get('filter')) + pageMessage = getPageMessageForWhom() + else: + pageMessage = None + try: + accounts = callGAPIpages(bp.accounts(), 'list', 'accounts', + pageMessage=pageMessage, + throwReasons=[GAPI.PERMISSION_DENIED], + **kwargs) + except GAPI.permissionDenied as e: + accessErrorExitNonDirectory(API.BUSINESSACCOUNTMANAGEMENT, str(e)) + if not csvPF: + jcount = len(accounts) + entityPerformActionNumItems([Ent.USER, user], jcount, Ent.BUSINESS_PROFILE_ACCOUNT, i, count) + Ind.Increment() + j = 0 + for account in sorted(accounts, key=lambda k: k['name']): + j += 1 + printKeyValueListWithCount(['Account', account['name']], j, jcount) + Ind.Increment() + showJSON(None, account) + Ind.Decrement() + Ind.Decrement() + else: + for account in accounts: + row = flattenJSON(account, flattened={'User': user, 'name': account['name'], 'accountName': account['accountName']}) + csvPF.WriteRowTitles(row) + if csvPF: + csvPF.writeCSVfile('Business Profile Accounts') diff --git a/src/gam/cmd/calendar.py b/src/gam/cmd/calendar.py index 4bdb4f02..57624a00 100644 --- a/src/gam/cmd/calendar.py +++ b/src/gam/cmd/calendar.py @@ -3,7 +3,8 @@ import re import json -from gam.util.csv_pf import RI_ENTITY, RI_J, RI_JCOUNT, RI_ITEM, FormatJSONQuoteChar +from gam.util.batch import batchRequestID, executeBatch, RI_ENTITY, RI_J, RI_JCOUNT, RI_ITEM +from gam.util.csv_pf import FormatJSONQuoteChar import uuid from gamlib import api as API @@ -45,7 +46,6 @@ from gam.util.csv_pf import ( CSVPrintFile, _getFieldsList, addFieldToFieldsList, - batchRequestID, cleanJSON, flattenJSON, getFieldsFromFieldsList, @@ -86,7 +86,7 @@ from gam.util.errors import ( unknownArgumentExit, ) from gam.util.fileio import UNKNOWN -from gam.util.output import executeBatch, formatKeyValueList, setSysExitRC +from gam.util.output import formatKeyValueList, setSysExitRC from gam.constants import DAYS_OF_WEEK, GOOGLE_MEETID_FORMAT_REQUIRED, GOOGLE_MEETID_PATTERN, NO_ENTITIES_FOUND_RC diff --git a/src/gam/cmd/chromeapps.py b/src/gam/cmd/chromeapps.py index 5add58c5..b3a41e4e 100644 --- a/src/gam/cmd/chromeapps.py +++ b/src/gam/cmd/chromeapps.py @@ -48,7 +48,6 @@ from gam.util.entity import _getCustomersCustomerIdWithC, convertEntityToList, g from gam.util.errors import missingArgumentExit, unknownArgumentExit from gam.util.fileio import UNKNOWN from gam.util.orgunits import getOrgUnitId -from gam.cmd.printers import CHROME_APPS_TIME_OBJECTS, CHROME_APPS_TYPE_CHOICES from gam.cmd.printers import ORGUNIT_ENTITIES_MAP diff --git a/src/gam/cmd/cigroups/groups.py b/src/gam/cmd/cigroups/groups.py index 594f7e2c..83d4fce8 100644 --- a/src/gam/cmd/cigroups/groups.py +++ b/src/gam/cmd/cigroups/groups.py @@ -16,12 +16,12 @@ from gamlib import msgs as Msg from gam.util.access import entityUnknownWarning from gam.util.api import buildGAPIObject from gam.util.api_call import callGAPI, callGAPIpages +from gam.util.output import formatLocalTime from gam.util.args import ( NEVER_TIME, _getOptionalIsSuspendedIsArchived, checkArgumentPresent, checkForExtraneousArguments, - formatLocalTime, getArgument, getChoice, getEmailAddress, diff --git a/src/gam/cmd/cigroups/members.py b/src/gam/cmd/cigroups/members.py index 6dde6cb5..703bbfb9 100644 --- a/src/gam/cmd/cigroups/members.py +++ b/src/gam/cmd/cigroups/members.py @@ -7,7 +7,6 @@ Part of the _cigroups_tmp sub-package.""" import re import json -from gam.util.args import formatLocalTime from gamlib import api as API from gamlib import settings as GC @@ -95,7 +94,7 @@ from gam.util.errors import ( ) from gam.util.fileio import UNKNOWN from gam.util.orgunits import _getOrgunitsOrgUnitIdPath -from gam.util.output import systemErrorExit, writeStdout +from gam.util.output import systemErrorExit, writeStdout, formatLocalTime CIGROUP_DISCUSSION_FORUM_LABEL = 'cloudidentity.googleapis.com/groups.discussion_forum' UNKNOWN = 'Unknown' diff --git a/src/gam/cmd/ciuserinvitations.py b/src/gam/cmd/ciuserinvitations.py index c59e8632..aff06d47 100644 --- a/src/gam/cmd/ciuserinvitations.py +++ b/src/gam/cmd/ciuserinvitations.py @@ -243,30 +243,7 @@ def checkCIUserIsInvitable(users): return csvPF.writeCSVfile('Invitable Users') -INBOUNDSSO_INPUT_MODE_CHOICE_MAP = { - 'saml': 'saml', - 'samlsso': 'saml', - 'oidc': 'oidc', - 'oidcsso': 'oidc', -} -INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP = { - 'all': 'all', - 'saml': 'saml', - 'samlsso': 'saml', - 'oidc': 'oidc', - 'oidcsso': 'oidc', -} -INBOUNDSSO_ALL_SAML = {'all', 'saml'} -INBOUNDSSO_ALL_OIDC = {'all', 'oidc'} -INBOUNDSSO_MODE_CHOICE_MAP = { - 'ssooff': 'SSO_OFF', - 'saml': 'SAML_SSO', - 'samlsso': 'SAML_SSO', - 'oidc': 'OIDC_SSO', - 'oidcsso': 'OIDC_SSO', - 'domainwidesamlifenabled': 'DOMAIN_WIDE_SAML_IF_ENABLED' - } diff --git a/src/gam/cmd/courses/content.py b/src/gam/cmd/courses/content.py index 6df04c5a..36683569 100644 --- a/src/gam/cmd/courses/content.py +++ b/src/gam/cmd/courses/content.py @@ -18,7 +18,6 @@ from gam.util.args import ( getArgument, getBoolean, getCharacter, - removeCourseIdScope, ) from gam.util.csv_pf import ( CSVPrintFile, @@ -30,7 +29,7 @@ from gam.util.csv_pf import ( getItemFieldsFromFieldsList, ) from gam.util.display import entityActionFailedWarning, entityDoesNotHaveItemWarning, getPageMessageForWhom, printGettingAllEntityItemsForWhom -from gam.util.entity import getEntityList +from gam.util.entity import getEntityList, removeCourseIdScope from gam.var import Cmd, Ent diff --git a/src/gam/cmd/courses/courses.py b/src/gam/cmd/courses/courses.py index 1f09f59e..8a90ecd8 100644 --- a/src/gam/cmd/courses/courses.py +++ b/src/gam/cmd/courses/courses.py @@ -18,7 +18,6 @@ from gam.util.api import buildGAPIObject from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages from gam.util.args import ( - addCourseIdScope, getAddCSVData, getArgument, getBoolean, @@ -32,8 +31,6 @@ from gam.util.args import ( getStringReturnInList, getStringWithCRsNLs, getTimeOrDeltaFromNow, - removeCourseAliasScope, - removeCourseIdScope, ) from gam.util.csv_pf import ( CSVPrintFile, @@ -63,7 +60,7 @@ from gam.util.display import ( printKeyValueList, printLine, ) -from gam.util.entity import getEntityList +from gam.util.entity import getEntityList, addCourseIdScope, removeCourseIdScope, removeCourseAliasScope from gam.util.errors import invalidChoiceExit, missingArgumentExit, unknownArgumentExit from gam.util.fileio import UNKNOWN from gam.util.output import currentCount, formatKeyValueList, writeStdout diff --git a/src/gam/cmd/courses/guardians.py b/src/gam/cmd/courses/guardians.py index e3bac32b..66302667 100644 --- a/src/gam/cmd/courses/guardians.py +++ b/src/gam/cmd/courses/guardians.py @@ -17,7 +17,6 @@ from gam.util.api import buildGAPIObject from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages from gam.util.args import ( - addCourseIdScope, checkForExtraneousArguments, getArgument, getChoice, @@ -48,7 +47,7 @@ from gam.util.display import ( printKeyValueListWithCount, printLine, ) -from gam.util.entity import getEntityArgument, getEntityList, getEntityToModify +from gam.util.entity import getEntityArgument, getEntityList, getEntityToModify, addCourseIdScope from gam.util.errors import entityActionFailedExit, invalidChoiceExit, missingArgumentExit, unknownArgumentExit from gam.util.output import ( currentCount, diff --git a/src/gam/cmd/courses/participants.py b/src/gam/cmd/courses/participants.py index 39dfd180..1fdea3d1 100644 --- a/src/gam/cmd/courses/participants.py +++ b/src/gam/cmd/courses/participants.py @@ -7,7 +7,7 @@ Part of the _courses_tmp sub-package.""" import re import json -from gam.util.csv_pf import RI_ENTITY, RI_J, RI_JCOUNT, RI_ITEM +from gam.util.batch import RI_ENTITY, RI_J, RI_JCOUNT, RI_ITEM from gamlib import api as API from gamlib import settings as GC @@ -18,8 +18,6 @@ from gam.util.api import buildGAPIObject, waitOnFailure from gam.util.api_call import callGAPI, checkGAPIError from gam.util.args import ( SORF_TEXT_ARGUMENTS, - addCourseAliasScope, - addCourseIdScope, checkArgumentPresent, checkForExtraneousArguments, getArgument, @@ -32,14 +30,14 @@ from gam.util.args import ( getStringReturnInList, getTimeOrDeltaFromNow, normalizeEmailAddressOrUID, - removeCourseAliasScope, - removeCourseIdScope, ) -from gam.util.csv_pf import CSVPrintFile, FormatJSONQuoteChar, batchRequestID, flattenJSON +from gam.util.batch import batchRequestID +from gam.util.csv_pf import CSVPrintFile, FormatJSONQuoteChar, flattenJSON from gam.util.display import entityActionFailedWarning, entityActionPerformed, entityActionPerformedMessage, entityPerformActionNumItems -from gam.util.entity import getEntityList, getEntityToModify, getItemsToModify +from gam.util.entity import getEntityList, getEntityToModify, getItemsToModify, addCourseIdScope, removeCourseIdScope, addCourseAliasScope, removeCourseAliasScope from gam.util.errors import missingArgumentExit, unknownArgumentExit -from gam.util.output import executeBatch, writeStdout +from gam.util.batch import executeBatch +from gam.util.output import writeStdout from gam.constants import OWNER_ACCESS_OPTIONS from gam.cmd.groups.groups import getSyncOperation from gam.var import Act, Cmd, Ent, Ind diff --git a/src/gam/cmd/cros.py b/src/gam/cmd/cros.py index e502cacb..56532ccb 100644 --- a/src/gam/cmd/cros.py +++ b/src/gam/cmd/cros.py @@ -2,9 +2,8 @@ import json -from gam.util.args import formatLocalTime -from gam.util.csv_pf import RI_J, RI_JCOUNT, RI_ITEM +from gam.util.batch import batchRequestID, executeBatch, RI_J, RI_JCOUNT, RI_ITEM import os import time @@ -18,7 +17,7 @@ from gam.util.access import checkEntityAFDNEorAccessErrorExit from gam.util.api import buildGAPIObject from gam.util.api_call import _finalizeGAPIpagesResult, _processGAPIpagesResult from gam.util.api_call import callGAPI, callGAPIitems, callGAPIpages, checkGAPIError -from gam.util.output import ISOformatTimeStamp +from gam.util.output import ISOformatTimeStamp, formatMilliSeconds from gam.util.args import ( SORTORDER_CHOICE_MAP, StartEndTime, @@ -27,7 +26,6 @@ from gam.util.args import ( _getFilterDateTime, checkArgumentPresent, escapeCRsNLs, - formatLocalDatestamp, getAddCSVData, getArgument, getBoolean, @@ -48,7 +46,6 @@ from gam.util.csv_pf import ( FormatJSONQuoteChar, _getFieldsList, addFieldToFieldsList, - batchRequestID, cleanJSON, flattenJSON, getFieldsFromFieldsList, @@ -97,7 +94,8 @@ from gam.util.errors import ( from gam.util.fileio import setFilePath, writeFile from gam.util.orgunits import getOrgUnitId, getOrgUnitItem from gam.util.output import ( - executeBatch, + formatLocalDatestamp, + formatLocalTime, stderrWarningMsg, systemErrorExit, writeStderr, diff --git a/src/gam/cmd/customer.py b/src/gam/cmd/customer.py index 0ec0a8e6..286fd14f 100644 --- a/src/gam/cmd/customer.py +++ b/src/gam/cmd/customer.py @@ -13,9 +13,7 @@ from gam.util.api_call import callGAPI from gam.util.args import ( LANGUAGE_CODES_MAP, YYYYMMDD_FORMAT, - formatLocalTime, - formatLocalTimestamp, - formatLocalTimestampUTC, + checkForExtraneousArguments, getArgument, getEmailAddress, getLanguageCode, @@ -32,10 +30,11 @@ from gam.util.display import ( ) from gam.util.errors import unknownArgumentExit from gam.util.fileio import UNKNOWN -from gam.util.output import printWarningMessage, writeStdout +from gam.util.output import printWarningMessage, writeStdout, formatLocalTime, formatLocalTimestamp, formatLocalTimestampUTC from gam.util.entity import ( _getCustomerId, _getDomainList, + setTrueCustomerId, ) from gam.constants import DATA_NOT_AVALIABLE_RC from gam.cmd.domains import CUSTOMER_LICENSE_MAP @@ -291,3 +290,8 @@ def doPrintShowDomains(): csvPF.writeCSVfile('Domains') +# gam info customerid +def doInfoCustomerId(): + checkForExtraneousArguments() + setTrueCustomerId(cd=None, forceUpdate=True) + writeStdout(f'{GC.Values[GC.CUSTOMER_ID]}\n') diff --git a/src/gam/cmd/datatransfer.py b/src/gam/cmd/datatransfer.py index d27115ec..27a0d64f 100644 --- a/src/gam/cmd/datatransfer.py +++ b/src/gam/cmd/datatransfer.py @@ -1,7 +1,6 @@ """GAM data transfer operations.""" -from gam.util.args import formatLocalTime import time from gamlib import api as API @@ -33,7 +32,7 @@ from gam.util.display import ( ) from gam.util.entity import convertEmailAddressToUID from gam.util.errors import entityActionFailedExit, invalidChoiceExit, unknownArgumentExit, usageErrorExit -from gam.util.output import writeStderr +from gam.util.output import writeStderr, formatLocalTime def getTransferApplications(dt): diff --git a/src/gam/cmd/domains.py b/src/gam/cmd/domains.py index ffacf56e..d667a18b 100644 --- a/src/gam/cmd/domains.py +++ b/src/gam/cmd/domains.py @@ -11,7 +11,7 @@ from gam.var import Act, Cmd, Ent, Ind from gam.util.access import accessErrorExit from gam.util.api import buildGAPIObject from gam.util.api_call import callGAPI, callGAPIitems -from gam.util.args import checkForExtraneousArguments, formatLocalTimestamp, getArgument, getString +from gam.util.args import checkForExtraneousArguments, getArgument, getString from gam.util.csv_pf import ( CSVPrintFile, DEFAULT_SKIP_OBJECTS, @@ -29,7 +29,7 @@ from gam.util.display import ( printLine, ) from gam.util.errors import missingArgumentExit, unknownArgumentExit -from gam.util.output import writeStdout +from gam.util.output import writeStdout, formatLocalTime, formatLocalTimestamp def doCreateDomainAlias(): diff --git a/src/gam/cmd/drive/activity.py b/src/gam/cmd/drive/activity.py index 3a15f261..33ceb389 100644 --- a/src/gam/cmd/drive/activity.py +++ b/src/gam/cmd/drive/activity.py @@ -6,7 +6,7 @@ import json -from gam.util.args import formatLocalTime +from gam.util.output import formatFileSize, formatLocalTime from gamlib import api as API from gamlib import settings as GC @@ -18,7 +18,6 @@ from gam.util.args import ( StartEndTime, checkArgumentPresent, checkForExtraneousArguments, - formatFileSize, getArgument, getCharacter, getChoice, diff --git a/src/gam/cmd/drive/copymove/copymove_util.py b/src/gam/cmd/drive/copymove/copymove_util.py index 241e681e..2688b0ce 100644 --- a/src/gam/cmd/drive/copymove/copymove_util.py +++ b/src/gam/cmd/drive/copymove/copymove_util.py @@ -21,7 +21,6 @@ from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages from gam.util.args import ( StartEndTime, - formatLocalTime, getAddCSVData, getArgument, getBoolean, @@ -52,7 +51,7 @@ from gam.util.entity import ( from gam.util.errors import deprecatedArgument, invalidChoiceExit, unknownArgumentExit, usageErrorExit from gam.util.fileio import UNKNOWN, closeFile from gam.util.gdoc import openCSVFileReader -from gam.util.output import writeStdout +from gam.util.output import writeStdout, formatLocalTime from gam.constants import ANY_NON_TRASHED_WITH_PARENTS, WITH_PARENTS from gam.var import Act, Cmd, Ent, Ind diff --git a/src/gam/cmd/drive/core.py b/src/gam/cmd/drive/core.py index 9648b3df..9661d0a1 100644 --- a/src/gam/cmd/drive/core.py +++ b/src/gam/cmd/drive/core.py @@ -24,7 +24,6 @@ from gam.util.api_call import callGAPI, callGAPIpages from gam.util.args import ( LANGUAGE_CODES_MAP, checkArgumentPresent, - formatLocalSecondsTimestamp, getBoolean, getChoice, getColor, @@ -61,7 +60,7 @@ from gam.util.errors import ( usageErrorExit, ) from gam.util.fileio import FILE_ERROR_RC, fileErrorMessage, setFilePath -from gam.util.output import setSysExitRC, stderrWarningMsg, systemErrorExit +from gam.util.output import setSysExitRC, stderrWarningMsg, systemErrorExit, formatLocalSecondsTimestamp from gam.constants import ANY_NON_TRASHED_FOLDER_NAME, MY_NON_TRASHED_FOLDER_NAME, NO_ENTITIES_FOUND_RC, TEAM_DRIVE from gam.var import Cmd, Ent @@ -82,6 +81,16 @@ MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut' MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk' MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site' MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet' +CORPORA_ALL_DRIVES = 'allDrives' +CORPORA_CHOICE_MAP = { + 'alldrives': CORPORA_ALL_DRIVES, + 'allshareddrives': CORPORA_ALL_DRIVES, + 'allteamdrives': CORPORA_ALL_DRIVES, + 'domain': 'domain', + 'onlyshareddrives': CORPORA_ALL_DRIVES, + 'onlyteamdrives': CORPORA_ALL_DRIVES, + 'user': 'user', + } ME_IN_OWNERS = "'me' in owners" ME_IN_OWNERS_AND = ME_IN_OWNERS + " and " NOT_ME_IN_OWNERS = "not " + ME_IN_OWNERS diff --git a/src/gam/cmd/drive/fileinfo.py b/src/gam/cmd/drive/fileinfo.py index fbb2a2f3..9833b183 100644 --- a/src/gam/cmd/drive/fileinfo.py +++ b/src/gam/cmd/drive/fileinfo.py @@ -67,8 +67,6 @@ from gam.util.api import buildGAPIObject from gam.util.api_call import callGAPI, callGAPIpages, yieldGAPIpages from gam.util.args import ( OrderBy, - formatFileSize, - formatLocalSecondsTimestamp, getAddCSVData, getArgument, getBoolean, @@ -97,7 +95,7 @@ from gam.util.entity import ( getEntityArgument, ) from gam.util.errors import invalidChoiceExit, unknownArgumentExit, usageErrorExit -from gam.util.output import writeStdout +from gam.util.output import writeStdout, formatFileSize, formatLocalSecondsTimestamp from gam.cmd.groups.members import finalizeInternalDomains SHARED_DRIVE_MAX_FILES_FOLDERS = 500000 diff --git a/src/gam/cmd/drive/filelist.py b/src/gam/cmd/drive/filelist.py index 42c30977..148617b8 100644 --- a/src/gam/cmd/drive/filelist.py +++ b/src/gam/cmd/drive/filelist.py @@ -6,7 +6,6 @@ import json -from gam.util.args import formatLocalTime from gam.cmd.drive.core import _getSharedDriveNameFromId, _mapDrive2QueryToDrive3, _simpleFileIdEntityList, _validateUserGetFileIDs, _validateUserSharedDrive, cleanFileIDsList, getDriveFileEntity, getDriveFileEntitySharedDriveOnly, initDriveFileEntity from gam.cmd.drive.filepaths import _finalizeIncludeLabels, _finalizeIncludePermissionsForView, _formatFileDriveLabels, _mapDriveInfo, _setGetCheckFilePermissions, _setGetPermissionsForMyDriveSharedDrives, _setSkipObjects, getFilePaths, initFilePathInfo @@ -95,7 +94,6 @@ from gam.util.api_call import callGAPI, callGAPIitems, callGAPIpages, yieldGAPIp from gam.util.args import ( NEVER_TIME, OrderBy, - formatFileSize, getAddCSVData, getArgument, getBoolean, @@ -135,6 +133,8 @@ from gam.util.fileio import UNKNOWN from gam.util.output import ( _stripControlCharsFromName, flushStderr, + formatFileSize, + formatLocalTime, setSysExitRC, writeStderr, writeStdout, diff --git a/src/gam/cmd/drive/filetree.py b/src/gam/cmd/drive/filetree.py index 942849b9..af290a5e 100644 --- a/src/gam/cmd/drive/filetree.py +++ b/src/gam/cmd/drive/filetree.py @@ -46,6 +46,7 @@ SHARED_DRIVES = 'SharedDrives' from gam.cmd.drive.core import ( MimeTypeCheck, + CORPORA_CHOICE_MAP, DRIVE_BY_NAME_CHOICE_MAP, LOCATION_ALL_DRIVES, LOCATION_CHOICE_MAP, diff --git a/src/gam/cmd/drive/permissions.py b/src/gam/cmd/drive/permissions.py index 4325a430..17a95ede 100644 --- a/src/gam/cmd/drive/permissions.py +++ b/src/gam/cmd/drive/permissions.py @@ -10,7 +10,7 @@ from gam.cmd.drive.core import _getDriveFileNameFromId, _getSharedDriveNameFromI from gam.cmd.drive.filepaths import _finalizeIncludePermissionsForView, _getIncludePermissionsForView, _mapDrivePermissionNames from gam.cmd.drive.filetree import _validateACLAttributes, _validateACLOwnerType -from gam.util.csv_pf import RI_ENTITY, RI_I, RI_COUNT, RI_J, RI_JCOUNT, RI_ITEM +from gam.util.batch import RI_ENTITY, RI_I, RI_COUNT, RI_J, RI_JCOUNT, RI_ITEM from gamlib import api as API from gamlib import settings as GC @@ -20,11 +20,11 @@ from gamlib import msgs as Msg from gam.util.api import _getAdminEmail, waitOnFailure from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages, checkGAPIError +from gam.util.output import formatLocalTime from gam.util.args import ( OrderBy, checkArgumentPresent, checkForExtraneousArguments, - formatLocalTime, getACLRoles, getArgument, getBoolean, @@ -41,7 +41,6 @@ from gam.util.csv_pf import ( CSVPrintFile, FormatJSONQuoteChar, _getFieldsList, - batchRequestID, cleanJSON, flattenJSON, getItemFieldsFromFieldsList, @@ -75,7 +74,7 @@ from gam.util.errors import ( usageErrorExit, ) from gam.util.fileio import UNKNOWN -from gam.util.output import executeBatch +from gam.util.batch import batchRequestID, executeBatch from gam.constants import ADMIN_ACCESS_OPTIONS, WITH_PARENTS from gam.var import Act, Cmd, Ent, Ind diff --git a/src/gam/cmd/drive/shareddrives.py b/src/gam/cmd/drive/shareddrives.py index a1561971..72cbd57d 100644 --- a/src/gam/cmd/drive/shareddrives.py +++ b/src/gam/cmd/drive/shareddrives.py @@ -7,7 +7,6 @@ import re import json -from gam.util.args import formatLocalTime from gam.cmd.drive.core import _convertSharedDriveNameToId, _getSharedDriveNameFromId from gam.cmd.drive.filepaths import _mapDrivePermissionNames @@ -81,7 +80,7 @@ from gam.util.errors import ( ) from gam.util.fileio import UNKNOWN from gam.util.orgunits import getOrgUnitId -from gam.util.output import setSysExitRC, systemErrorExit, writeStderr, writeStdout +from gam.util.output import setSysExitRC, systemErrorExit, writeStderr, writeStdout, formatLocalTime from gam.constants import ADMIN_ACCESS_OPTIONS, GOOGLE_API_ERROR_RC, NO_ENTITIES_FOUND_RC from gam.cmd.orgunits import getOrgUnitIdToPathMap diff --git a/src/gam/cmd/drive/transfer/fileops.py b/src/gam/cmd/drive/transfer/fileops.py index 0587e61e..6691e9be 100644 --- a/src/gam/cmd/drive/transfer/fileops.py +++ b/src/gam/cmd/drive/transfer/fileops.py @@ -69,7 +69,7 @@ from gam.util.fileio import ( uniqueFilename, writeFile, ) -from gam.util.output import setSysExitRC, writeStderr +from gam.util.output import setSysExitRC, writeStderr, formatFileSize from gam.constants import MY_NON_TRASHED_FOLDER_NAME from gam.util.tags import _substituteForUser diff --git a/src/gam/cmd/drive/transfer/ownership.py b/src/gam/cmd/drive/transfer/ownership.py index fcddde12..4e2ccc0b 100644 --- a/src/gam/cmd/drive/transfer/ownership.py +++ b/src/gam/cmd/drive/transfer/ownership.py @@ -18,7 +18,6 @@ from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages from gam.util.args import ( OrderBy, - formatFileSize, getArgument, getBoolean, getCharacter, @@ -54,7 +53,7 @@ from gam.util.entity import ( ) from gam.util.errors import unknownArgumentExit, usageErrorExit from gam.util.fileio import UNKNOWN -from gam.util.output import formatKeyValueList, printWarningMessage, systemErrorExit +from gam.util.output import formatKeyValueList, printWarningMessage, systemErrorExit, formatFileSize from gam.constants import MY_DRIVE, MY_NON_TRASHED_FOLDER_NAME, MY_NON_TRASHED_FOLDER_NAME_WITH_PARENTS, NON_TRASHED, TARGET_DRIVE_SPACE_ERROR_RC, WITH_PARENTS from gam.util.tags import _substituteForUser diff --git a/src/gam/cmd/gmail/filters.py b/src/gam/cmd/gmail/filters.py index ecc17b1f..881e93e6 100644 --- a/src/gam/cmd/gmail/filters.py +++ b/src/gam/cmd/gmail/filters.py @@ -41,7 +41,7 @@ from gam.util.display import ( ) from gam.util.entity import _validateUserGetObjectList, getEntityArgument, getUserObjectEntity from gam.util.errors import missingChoiceExit, unknownArgumentExit, usageErrorExit -from gam.util.output import ERROR +from gam.util.output import ERROR, formatMaxMessageBytes from gam.var import Act, Cmd, Ent, Ind diff --git a/src/gam/cmd/gmail/labels.py b/src/gam/cmd/gmail/labels.py index fc57de33..2cf39b3c 100644 --- a/src/gam/cmd/gmail/labels.py +++ b/src/gam/cmd/gmail/labels.py @@ -6,7 +6,7 @@ Part of the _gmail_monolith sub-package.""" import re -from gam.util.csv_pf import RI_ENTITY, RI_J, RI_JCOUNT, RI_ITEM +from gam.util.batch import RI_ENTITY, RI_J, RI_JCOUNT, RI_ITEM from gamlib import api as API from gamlib import gapi as GAPI @@ -28,7 +28,8 @@ from gam.util.args import ( validateREPattern, validateREPatternSubstitution, ) -from gam.util.csv_pf import CSVPrintFile, batchRequestID, flattenJSON +from gam.util.batch import batchRequestID +from gam.util.csv_pf import CSVPrintFile, flattenJSON from gam.util.display import ( entityActionFailedWarning, entityActionNotPerformedWarning, @@ -45,7 +46,8 @@ from gam.util.display import ( ) from gam.util.entity import getEntityArgument, getEntityList from gam.util.errors import missingArgumentExit, unknownArgumentExit, usageErrorExit -from gam.util.output import executeBatch, setSysExitRC +from gam.util.batch import executeBatch +from gam.util.output import setSysExitRC from gam.constants import NO_ENTITIES_FOUND_RC from gam.var import Act, Cmd, Ent, Ind diff --git a/src/gam/cmd/gmail/messages.py b/src/gam/cmd/gmail/messages.py index 440c7109..3fe2751d 100644 --- a/src/gam/cmd/gmail/messages.py +++ b/src/gam/cmd/gmail/messages.py @@ -6,12 +6,12 @@ Part of the _gmail_monolith sub-package.""" import re -from gam.util.args import formatLocalTimestamp + import googleapiclient.errors import googleapiclient.http -from gam.util.csv_pf import RI_ENTITY, RI_I, RI_COUNT, RI_J, RI_JCOUNT, RI_ITEM +from gam.util.batch import RI_ENTITY, RI_I, RI_COUNT, RI_J, RI_JCOUNT, RI_ITEM from gam.cmd.gmail.labels import _getUserGmailLabels, _initLabelNameMap, _convertLabelNamesToIds, MESSAGES_MAX_TO_KEYWORDS import io import base64 @@ -46,7 +46,8 @@ from gam.util.args import ( getTimeOrDeltaFromNow, splitEmailAddress, ) -from gam.util.csv_pf import CSVPrintFile, batchRequestID, flattenJSON +from gam.util.batch import batchRequestID +from gam.util.csv_pf import CSVPrintFile, flattenJSON from gam.util.display import ( entityActionFailedWarning, entityActionNotPerformedWarning, @@ -83,7 +84,8 @@ from gam.util.fileio import ( uniqueFilename, writeFileReturnError, ) -from gam.util.output import executeBatch, setSysExitRC, stderrWarningMsg +from gam.util.batch import executeBatch +from gam.util.output import setSysExitRC, stderrWarningMsg, formatLocalTime, formatLocalTimestamp from gam.constants import IS08601_TIME_FORMAT, NO_ENTITIES_FOUND_RC, RFC2822_TIME_FORMAT from gam.util.tags import ( _getTagReplacement, diff --git a/src/gam/cmd/gmail/signature.py b/src/gam/cmd/gmail/signature.py index c3dca7fa..9bca2020 100644 --- a/src/gam/cmd/gmail/signature.py +++ b/src/gam/cmd/gmail/signature.py @@ -11,12 +11,12 @@ from gamlib import api as API from gamlib import gapi as GAPI from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI +from gam.util.output import formatLocalDatestamp from gam.util.args import ( FALSE_VALUES, SORF_MSG_FILE_ARGUMENTS, TRUE_VALUES, escapeCRsNLs, - formatLocalDatestamp, getArgument, getBoolean, getString, diff --git a/src/gam/cmd/gmail/smime.py b/src/gam/cmd/gmail/smime.py index f308a0fd..77968fbb 100644 --- a/src/gam/cmd/gmail/smime.py +++ b/src/gam/cmd/gmail/smime.py @@ -5,7 +5,7 @@ Part of the _gmail_monolith sub-package.""" """GAM Gmail management: labels, messages, filters, forwarding, sendas, S/MIME, CSE, vacation.""" -from gam.util.args import formatLocalTimestamp + import base64 from gamlib import api as API @@ -32,7 +32,7 @@ from gam.util.display import ( from gam.util.entity import getEntityArgument from gam.util.errors import missingArgumentExit, unknownArgumentExit from gam.util.fileio import readFile, setFilePath -from gam.util.output import setSysExitRC +from gam.util.output import setSysExitRC, formatLocalTime, formatLocalTimestamp from gam.constants import NO_ENTITIES_FOUND_RC from gam.var import Act, Cmd, Ent, Ind diff --git a/src/gam/cmd/groups/groups.py b/src/gam/cmd/groups/groups.py index 3e86f994..65cf5eba 100644 --- a/src/gam/cmd/groups/groups.py +++ b/src/gam/cmd/groups/groups.py @@ -6,7 +6,7 @@ Part of the _groups_tmp sub-package.""" import re -from gam.util.csv_pf import RI_ENTITY, RI_ROLE, RI_I, RI_COUNT, RI_J, RI_JCOUNT, RI_ITEM +from gam.util.batch import RI_ENTITY, RI_ROLE, RI_I, RI_COUNT, RI_J, RI_JCOUNT, RI_ITEM from gam.util.entity import GROUP_ROLES_MAP import time @@ -45,7 +45,8 @@ from gam.util.args import ( getStringWithCRsNLs, splitEmailAddress, ) -from gam.util.csv_pf import CSVPrintFile, batchRequestID +from gam.util.batch import batchRequestID +from gam.util.csv_pf import CSVPrintFile from gam.util.display import ( entityActionFailedWarning, entityActionNotPerformedWarning, diff --git a/src/gam/cmd/groups/members.py b/src/gam/cmd/groups/members.py index aa1c51fd..66942ba2 100644 --- a/src/gam/cmd/groups/members.py +++ b/src/gam/cmd/groups/members.py @@ -7,11 +7,10 @@ Part of the _groups_tmp sub-package.""" import re import json -from gam.util.args import formatLocalTime from gam.cmd.groups.groups import GroupIsAbuseOrPostmaster, getGroupAttrProperties, getGroupAttrValue, getGroupMemberTypes, mapGroupEmailForSettings -from gam.util.csv_pf import RI_ENTITY, RI_ROLE, RI_COUNT +from gam.util.batch import RI_ENTITY, RI_ROLE, RI_COUNT from gam.util.entity import GROUP_ROLES_MAP @@ -38,7 +37,6 @@ from gam.util.args import ( checkArgumentPresent, checkForExtraneousArguments, escapeCRsNLs, - formatMaxMessageBytes, getAddCSVData, getArgument, getBoolean, @@ -51,12 +49,12 @@ from gam.util.args import ( getString, normalizeEmailAddressOrUID, ) +from gam.util.batch import batchRequestID, executeBatch from gam.util.csv_pf import ( CSVPrintFile, FormatJSONQuoteChar, _getFieldsList, addFieldToFieldsList, - batchRequestID, cleanJSON, flattenJSON, getFieldsFromFieldsList, @@ -110,7 +108,7 @@ from gam.util.domain_filters import ( makeUserGroupDomainQueryFilters, ) from gam.util.schema_utils import _initSchemaParms, _getSchemaNameList -from gam.util.output import executeBatch, writeStderr, writeStdout +from gam.util.output import writeStderr, writeStdout, formatMaxMessageBytes, formatLocalTime def initMemberOptions(): return [False, False, False, False, None, None, False, None, True] diff --git a/src/gam/cmd/mobile.py b/src/gam/cmd/mobile.py index 0a6409f3..e5cee953 100644 --- a/src/gam/cmd/mobile.py +++ b/src/gam/cmd/mobile.py @@ -12,8 +12,6 @@ from gam.util.api_call import callGAPI, callGAPIpages, yieldGAPIpages from gam.util.args import ( UTF8, checkArgumentPresent, - formatLocalTime, - formatLocalTimestamp, getArgument, getCharacter, getChoice, @@ -50,7 +48,7 @@ from gam.util.display import ( from gam.util.entity import _validateDeviceQuery, getDeviceQueries, getEntityList, getEntityToModify from gam.util.errors import unknownArgumentExit, usageErrorExit from gam.util.fileio import UNKNOWN -from gam.util.output import writeStdout +from gam.util.output import writeStdout, formatLocalTime, formatLocalTimestamp from gam.constants import PROJECTION_CHOICE_MAP diff --git a/src/gam/cmd/notes.py b/src/gam/cmd/notes.py index 1f9b459f..c74cf4f2 100644 --- a/src/gam/cmd/notes.py +++ b/src/gam/cmd/notes.py @@ -13,11 +13,11 @@ from gamlib import msgs as Msg from gam.var import Act, Cmd, Ent, Ind from gam.util.svcacct import buildGAPIServiceObject from gam.util.api_call import callGAPI, callGAPIpages +from gam.util.output import formatLocalTime from gam.util.args import ( SORF_TEXT_ARGUMENTS, checkForExtraneousArguments, escapeCRsNLs, - formatLocalTime, getArgument, getBoolean, getChoice, diff --git a/src/gam/cmd/orgunits.py b/src/gam/cmd/orgunits.py index 8c71da87..f55b0b80 100644 --- a/src/gam/cmd/orgunits.py +++ b/src/gam/cmd/orgunits.py @@ -2,7 +2,6 @@ import json -from gam.util.csv_pf import RI_I, RI_J, RI_JCOUNT, RI_ITEM import time from gamlib import api as API @@ -36,11 +35,11 @@ from gam.util.args import ( normalizeEmailAddressOrUID, orgUnitPathQuery, ) +from gam.util.batch import batchRequestID, executeBatch, RI_I, RI_J, RI_JCOUNT, RI_ITEM from gam.util.csv_pf import ( CSVPrintFile, FormatJSONQuoteChar, _getFieldsList, - batchRequestID, cleanJSON, flattenJSON, getFieldsFromFieldsList, @@ -76,7 +75,6 @@ from gam.util.errors import ( from gam.util.fileio import DEFAULT_FILE_WRITE_MODE, closeFile, openFile, setFilePath from gam.util.orgunits import getOrgUnitId, getOrgUnitItem, getTopLevelOrgId from gam.util.output import ( - executeBatch, setSysExitRC, systemErrorExit, writeStderr, diff --git a/src/gam/cmd/people.py b/src/gam/cmd/people.py index e53271bd..62bcdbd6 100644 --- a/src/gam/cmd/people.py +++ b/src/gam/cmd/people.py @@ -80,7 +80,6 @@ from gam.util.args import ( UID_PATTERN, UTF8, checkForExtraneousArguments, - formatLocalTime, getArgument, getBoolean, getChoice, @@ -121,7 +120,7 @@ from gam.util.display import ( from gam.util.entity import getEntityArgument, getEntityList from gam.util.errors import deprecatedArgument, invalidChoiceExit, missingArgumentExit, unknownArgumentExit from gam.util.fileio import UNKNOWN, setFilePath, writeFileReturnError -from gam.util.output import setSysExitRC, writeStdout +from gam.util.output import setSysExitRC, writeStdout, formatLocalTime from gam.constants import NO_ENTITIES_FOUND_RC from gam.cmd.contacts import normalizeContactGroupResourceName from gam.cmd.contacts import PeopleManager diff --git a/src/gam/cmd/printers.py b/src/gam/cmd/printers.py index d1c2f7f3..84625259 100644 --- a/src/gam/cmd/printers.py +++ b/src/gam/cmd/printers.py @@ -423,7 +423,5 @@ def doPrintShowPrinterModels(): if csvPF: csvPF.writeCSVfile('Printer Models') -CHROME_APPS_TIME_OBJECTS = {'firstPublishTime', 'latestPublishTime'} -CHROME_APPS_TYPE_CHOICES = ['android', 'chrome', 'web'] # gam info chromeapp android|chrome|web [formatjson] diff --git a/src/gam/cmd/project.py b/src/gam/cmd/project.py index f3600e89..4d3067b0 100644 --- a/src/gam/cmd/project.py +++ b/src/gam/cmd/project.py @@ -47,7 +47,6 @@ from gam.util.args import ( UTF8, checkArgumentPresent, checkForExtraneousArguments, - formatLocalTime, getArgument, getCharacter, getChoice, @@ -92,6 +91,7 @@ from gam.util.output import ( createRedText, createYellowText, currentCount, + formatLocalTime, readStdin, setSysExitRC, systemErrorExit, @@ -608,11 +608,6 @@ def getCRMOrgId(forceSearch=False): return orgs[0].get('name') return GC.Values[GC.GCP_ORG_ID] -# gam info customerid -def doInfoCustomerId(): - checkForExtraneousArguments() - setTrueCustomerId(cd=None, forceUpdate=True) - writeStdout(f'{GC.Values[GC.CUSTOMER_ID]}\n') # gam info gcporgid def doInfoGCPOrgId(): diff --git a/src/gam/cmd/reports.py b/src/gam/cmd/reports.py index 116b0b36..1f42664d 100644 --- a/src/gam/cmd/reports.py +++ b/src/gam/cmd/reports.py @@ -22,7 +22,6 @@ from gam.util.args import ( YYYYMMDD_FORMAT, YYYYMMDD_FORMAT_REQUIRED, escapeCRsNLs, - formatLocalTime, getAddCSVData, getArgument, getBoolean, @@ -52,6 +51,7 @@ from gam.util.errors import invalidArgumentExit, invalidChoiceExit, unknownArgum from gam.util.fileio import UNKNOWN from gam.util.orgunits import getOrgUnitId from gam.util.output import ( + formatLocalTime, printErrorMessage, printWarningMessage, setSysExitRC, diff --git a/src/gam/cmd/sites.py b/src/gam/cmd/sites.py index 696ff41c..d6f034e7 100644 --- a/src/gam/cmd/sites.py +++ b/src/gam/cmd/sites.py @@ -31,12 +31,21 @@ from gam.util.errors import INVALID_JSON_RC, deprecatedCommandExit, unknownArgum from gam.util.fileio import writeFile from gam.util.output import ERROR, systemErrorExit from gam.constants import NETWORK_ERROR_RC -from gam.cmd.sso import SITEVERIFICATION_METHOD_CHOICE_MAP from urllib.parse import unquote from urllib.parse import urlencode +SITEVERIFICATION_METHOD_CHOICE_MAP = { + 'cname': 'DNS_CNAME', + 'dnscname': 'DNS_CNAME', + 'dnstxt': 'DNS_TXT', + 'txt': 'DNS_TXT', + 'text': 'DNS_TXT', + 'file': 'FILE', + 'site': 'FILE', + } + def deprecatedUserSites(_): deprecatedCommandExit() diff --git a/src/gam/cmd/sso.py b/src/gam/cmd/sso.py index bd44ed35..fc19aad3 100644 --- a/src/gam/cmd/sso.py +++ b/src/gam/cmd/sso.py @@ -39,14 +39,34 @@ from gam.util.errors import missingArgumentExit, unknownArgumentExit, usageError from gam.util.fileio import readFile, setFilePath, writeFile from gam.util.orgunits import getOrgUnitId from gam.util.output import writeStderr, writeStdout -from gam.cmd.ciuserinvitations import INBOUNDSSO_ALL_OIDC, INBOUNDSSO_ALL_SAML from gam.cmd.reseller import normalizeChannelCustomerID -from gam.cmd.ciuserinvitations import INBOUNDSSO_INPUT_MODE_CHOICE_MAP -from gam.cmd.ciuserinvitations import INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP from gam.cmd.project import _generatePrivateKeyAndPublicCert -from gam.cmd.ciuserinvitations import INBOUNDSSO_MODE_CHOICE_MAP +INBOUNDSSO_INPUT_MODE_CHOICE_MAP = { + 'saml': 'saml', + 'samlsso': 'saml', + 'oidc': 'oidc', + 'oidcsso': 'oidc', +} +INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP = { + 'all': 'all', + 'saml': 'saml', + 'samlsso': 'saml', + 'oidc': 'oidc', + 'oidcsso': 'oidc', +} +INBOUNDSSO_ALL_SAML = {'all', 'saml'} +INBOUNDSSO_ALL_OIDC = {'all', 'oidc'} +INBOUNDSSO_MODE_CHOICE_MAP = { + 'ssooff': 'SSO_OFF', + 'saml': 'SAML_SSO', + 'samlsso': 'SAML_SSO', + 'oidc': 'OIDC_SSO', + 'oidcsso': 'OIDC_SSO', + 'domainwidesamlifenabled': 'DOMAIN_WIDE_SAML_IF_ENABLED' + } + def getCIOrgunitID(cd, orgunit): ou_id = getOrgUnitId(cd, orgunit)[1] if ou_id.startswith('id:'): @@ -820,14 +840,5 @@ def doPrintShowInboundSSOAssignments(): if csvPF: csvPF.writeCSVfile('Inbound SSO Assignments') -SITEVERIFICATION_METHOD_CHOICE_MAP = { - 'cname': 'DNS_CNAME', - 'dnscname': 'DNS_CNAME', - 'dnstxt': 'DNS_TXT', - 'txt': 'DNS_TXT', - 'text': 'DNS_TXT', - 'file': 'FILE', - 'site': 'FILE', - } # gam create verify|verification diff --git a/src/gam/cmd/tagmanager.py b/src/gam/cmd/tagmanager.py new file mode 100644 index 00000000..31e4ab09 --- /dev/null +++ b/src/gam/cmd/tagmanager.py @@ -0,0 +1,177 @@ +"""GAM Tag Manager account, container, workspace, tag, and permission management.""" + +import json + +from gamlib import api as API +from gamlib import settings as GC +from gamlib import gapi as GAPI +from gam.var import Act, Cmd, Ent, Ind +from gam.util.svcacct import buildGAPIServiceObject +from gam.util.api_call import callGAPIpages +from gam.util.args import ( + checkArgumentPresent, + getArgument, + getBoolean, + getString, +) +from gam.util.csv_pf import ( + CSVPrintFile, + FormatJSONQuoteChar, + cleanJSON, + flattenJSON, + showJSON, +) +from gam.util.display import ( + entityActionFailedWarning, + entityPerformActionNumItems, + getPageMessageForWhom, + printEntity, + printGettingAllEntityItemsForWhom, + printKeyValueList, + printLine, +) +from gam.util.entity import getEntityArgument, getEntityList + + +TAGMANAGER_PARAMETERS = { + Ent.TAGMANAGER_ACCOUNT: {'api': API.TAGMANAGER, 'respType': 'account', 'parentEntityType': None, + 'name': 'name', 'idList': ['accountId']}, + Ent.TAGMANAGER_CONTAINER: {'api': API.TAGMANAGER, 'respType': 'container', 'parentEntityType': Ent.TAGMANAGER_ACCOUNT, + 'name': 'name', 'idList': ['accountId', 'containerId']}, + Ent.TAGMANAGER_WORKSPACE: {'api': API.TAGMANAGER, 'respType': 'workspace', 'parentEntityType': Ent.TAGMANAGER_CONTAINER, + 'name': 'name', 'idList': ['accountId', 'containerId', 'workspaceId']}, + Ent.TAGMANAGER_TAG: {'api': API.TAGMANAGER, 'respType': 'tag', 'parentEntityType': Ent.TAGMANAGER_WORKSPACE, + 'name': 'name', 'idList': ['accountId', 'containerId', 'workspaceId', 'tagId']}, + Ent.TAGMANAGER_PERMISSION: {'api': API.TAGMANAGER_USERS, 'respType': 'userPermission', 'parentEntityType': Ent.TAGMANAGER_ACCOUNT, + 'name': 'emailAddress', 'idList': ['accountId']}, + } + +def printShowTagManagerObjects(users, entityType): + csvPF = CSVPrintFile(['User']) if Act.csvFormat() else None + FJQC = FormatJSONQuoteChar(csvPF) + if entityType == Ent.TAGMANAGER_ACCOUNT: + kwargs = {'includeGoogleTags': False} + parentList = [None] + else: + kwargs = {'parent': None} + if not checkArgumentPresent('select'): + parentList = getString(Cmd.OB_TAGMANAGER_PATH_LIST).replace(',', ' ').split() + else: + parentList = getEntityList(Cmd.OB_TAGMANAGER_PATH_LIST) + parameters = TAGMANAGER_PARAMETERS[entityType] + if csvPF: + csvPF.AddTitles([parameters['name'], 'path']) + while Cmd.ArgumentsRemaining(): + myarg = getArgument() + if csvPF and myarg == 'todrive': + csvPF.GetTodriveParameters() + elif entityType == Ent.TAGMANAGER_ACCOUNT and myarg == 'includegoogletags': + kwargs['includeGoogleTags'] = getBoolean() + else: + FJQC.GetFormatJSONQuoteChar(myarg, True) + i, count, users = getEntityArgument(users) + for user in users: + i += 1 + user, svc = buildGAPIServiceObject(parameters['api'], user, i, count) + if not svc: + continue + if entityType == Ent.TAGMANAGER_ACCOUNT: + svc = svc.accounts() + elif entityType == Ent.TAGMANAGER_CONTAINER: + svc = svc.accounts().containers() + elif entityType == Ent.TAGMANAGER_WORKSPACE: + svc = svc.accounts().containers().workspaces() + elif entityType == Ent.TAGMANAGER_TAG: + svc = svc.accounts().containers().workspaces().tags() + else: #elif entityType == Ent.TAGMANAGER_PERMISSION: + svc = svc.accounts().user_permissions() + jcount = len(parentList) + j = 0 + for parent in parentList: + j += 1 + if entityType == Ent.TAGMANAGER_ACCOUNT: + printGettingAllEntityItemsForWhom(entityType, user, i, count) + else: + kwargs['parent'] = parent + qualifier = f' for {Ent.Singular(parameters["parentEntityType"])}: {parent}' + printGettingAllEntityItemsForWhom(entityType, user, i, count, qualifier=qualifier) + try: + results = callGAPIpages(svc, 'list', parameters['respType'], + pageMessage=getPageMessageForWhom(), + throwReasons=GAPI.TAGMANAGER_THROW_REASONS, + **kwargs) + except (GAPI.badRequest, GAPI.invalid, GAPI.notFound) as e: + entityActionFailedWarning([Ent.USER, user, entityType, kwargs['parent']], str(e), j, jcount) + continue + if not csvPF: + kcount = len(results) + if not FJQC.formatJSON: + entityPerformActionNumItems([Ent.USER, user], kcount, entityType, j, jcount) + Ind.Increment() + k = 0 + for result in results: + k += 1 + if not FJQC.formatJSON: + printEntity([entityType, result['path']], k, kcount) + Ind.Increment() + printKeyValueList([parameters['name'], result.pop(parameters['name'])]) + for tmid in parameters['idList']: + printKeyValueList([tmid, result.pop(tmid)]) + showJSON(None, result) + Ind.Decrement() + else: + printLine(json.dumps(cleanJSON(result), ensure_ascii=False, sort_keys=True)) + Ind.Decrement() + elif results: + for result in results: + baseRow = {'User': user} + for tmid in parameters['idList']: + baseRow[tmid] = result.pop(tmid) + row = flattenJSON(result, flattened=baseRow) + if not FJQC.formatJSON: + csvPF.WriteRowTitles(row) + elif csvPF.CheckRowTitles(row): + row = {'User': user, parameters['name']: result[parameters['name']], 'path': result['path']} + row['JSON'] = json.dumps(cleanJSON(result), ensure_ascii=False, sort_keys=True) + csvPF.WriteRowNoFilter(row) + elif GC.Values[GC.CSV_OUTPUT_USERS_AUDIT]: + csvPF.WriteRowNoFilter({'User': user}) + if csvPF: + csvPF.writeCSVfile(Ent.Plural(entityType)) + +# gam show tagmanageraccounts +# [includegoogletags []] +# [formatjson] +# gam print tagmanagerccounts [todrive *] +# [includegoogletags []] +# [formatjson [quotechar ]] +def printShowTagManagerAccounts(users): + printShowTagManagerObjects(users, Ent.TAGMANAGER_ACCOUNT) + +# gam show tagmanagercontainers +# [formatjson] +# gam print tagmanagercontainers [todrive *] +# [formatjson [quotechar ]] +def printShowTagManagerContainers(users): + printShowTagManagerObjects(users, Ent.TAGMANAGER_CONTAINER) + +# gam show tagmanagerworkspaces +# [formatjson] +# gam print tagmanagerworkspaces +# [formatjson [quotechar ]] +def printShowTagManagerWorkspaces(users): + printShowTagManagerObjects(users, Ent.TAGMANAGER_WORKSPACE) + +# gam show tagmanagertags +# [formatjson] +# gam print tagmanagertags [todrive *] +# [formatjson [quotechar ]] +def printShowTagManagerTags(users): + printShowTagManagerObjects(users, Ent.TAGMANAGER_TAG) + +# gam show tagmanagerpermissions +# [formatjson] +# gam print tagmanagerpermissions [todrive *] +# [formatjson [quotechar ]] +def printShowTagManagerPermissions(users): + printShowTagManagerObjects(users, Ent.TAGMANAGER_PERMISSION) diff --git a/src/gam/cmd/tasks.py b/src/gam/cmd/tasks.py index decc53de..19df55f0 100644 --- a/src/gam/cmd/tasks.py +++ b/src/gam/cmd/tasks.py @@ -1,4 +1,4 @@ -"""GAM Google Tasks and Tag Manager management.""" +"""GAM Google Tasks management.""" import json @@ -651,146 +651,3 @@ def importTasklist(users): tasklist=tasklistId, parent=parent, body=task) parentIdMap[taskId] = result['id'] -TAGMANAGER_PARAMETERS = { - Ent.TAGMANAGER_ACCOUNT: {'api': API.TAGMANAGER, 'respType': 'account', 'parentEntityType': None, - 'name': 'name', 'idList': ['accountId']}, - Ent.TAGMANAGER_CONTAINER: {'api': API.TAGMANAGER, 'respType': 'container', 'parentEntityType': Ent.TAGMANAGER_ACCOUNT, - 'name': 'name', 'idList': ['accountId', 'containerId']}, - Ent.TAGMANAGER_WORKSPACE: {'api': API.TAGMANAGER, 'respType': 'workspace', 'parentEntityType': Ent.TAGMANAGER_CONTAINER, - 'name': 'name', 'idList': ['accountId', 'containerId', 'workspaceId']}, - Ent.TAGMANAGER_TAG: {'api': API.TAGMANAGER, 'respType': 'tag', 'parentEntityType': Ent.TAGMANAGER_WORKSPACE, - 'name': 'name', 'idList': ['accountId', 'containerId', 'workspaceId', 'tagId']}, - Ent.TAGMANAGER_PERMISSION: {'api': API.TAGMANAGER_USERS, 'respType': 'userPermission', 'parentEntityType': Ent.TAGMANAGER_ACCOUNT, - 'name': 'emailAddress', 'idList': ['accountId']}, - } - -def printShowTagManagerObjects(users, entityType): - csvPF = CSVPrintFile(['User']) if Act.csvFormat() else None - FJQC = FormatJSONQuoteChar(csvPF) - if entityType == Ent.TAGMANAGER_ACCOUNT: - kwargs = {'includeGoogleTags': False} - parentList = [None] - else: - kwargs = {'parent': None} - if not checkArgumentPresent('select'): - parentList = getString(Cmd.OB_TAGMANAGER_PATH_LIST).replace(',', ' ').split() - else: - parentList = getEntityList(Cmd.OB_TAGMANAGER_PATH_LIST) - parameters = TAGMANAGER_PARAMETERS[entityType] - if csvPF: - csvPF.AddTitles([parameters['name'], 'path']) - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if csvPF and myarg == 'todrive': - csvPF.GetTodriveParameters() - elif entityType == Ent.TAGMANAGER_ACCOUNT and myarg == 'includegoogletags': - kwargs['includeGoogleTags'] = getBoolean() - else: - FJQC.GetFormatJSONQuoteChar(myarg, True) - i, count, users = getEntityArgument(users) - for user in users: - i += 1 - user, svc = buildGAPIServiceObject(parameters['api'], user, i, count) - if not svc: - continue - if entityType == Ent.TAGMANAGER_ACCOUNT: - svc = svc.accounts() - elif entityType == Ent.TAGMANAGER_CONTAINER: - svc = svc.accounts().containers() - elif entityType == Ent.TAGMANAGER_WORKSPACE: - svc = svc.accounts().containers().workspaces() - elif entityType == Ent.TAGMANAGER_TAG: - svc = svc.accounts().containers().workspaces().tags() - else: #elif entityType == Ent.TAGMANAGER_PERMISSION: - svc = svc.accounts().user_permissions() - jcount = len(parentList) - j = 0 - for parent in parentList: - j += 1 - if entityType == Ent.TAGMANAGER_ACCOUNT: - printGettingAllEntityItemsForWhom(entityType, user, i, count) - else: - kwargs['parent'] = parent - qualifier = f' for {Ent.Singular(parameters["parentEntityType"])}: {parent}' - printGettingAllEntityItemsForWhom(entityType, user, i, count, qualifier=qualifier) - try: - results = callGAPIpages(svc, 'list', parameters['respType'], - pageMessage=getPageMessageForWhom(), - throwReasons=GAPI.TAGMANAGER_THROW_REASONS, - **kwargs) - except (GAPI.badRequest, GAPI.invalid, GAPI.notFound) as e: - entityActionFailedWarning([Ent.USER, user, entityType, kwargs['parent']], str(e), j, jcount) - continue - if not csvPF: - kcount = len(results) - if not FJQC.formatJSON: - entityPerformActionNumItems([Ent.USER, user], kcount, entityType, j, jcount) - Ind.Increment() - k = 0 - for result in results: - k += 1 - if not FJQC.formatJSON: - printEntity([entityType, result['path']], k, kcount) - Ind.Increment() - printKeyValueList([parameters['name'], result.pop(parameters['name'])]) - for tmid in parameters['idList']: - printKeyValueList([tmid, result.pop(tmid)]) - showJSON(None, result) - Ind.Decrement() - else: - printLine(json.dumps(cleanJSON(result), ensure_ascii=False, sort_keys=True)) - Ind.Decrement() - elif results: - for result in results: - baseRow = {'User': user} - for tmid in parameters['idList']: - baseRow[tmid] = result.pop(tmid) - row = flattenJSON(result, flattened=baseRow) - if not FJQC.formatJSON: - csvPF.WriteRowTitles(row) - elif csvPF.CheckRowTitles(row): - row = {'User': user, parameters['name']: result[parameters['name']], 'path': result['path']} - row['JSON'] = json.dumps(cleanJSON(result), ensure_ascii=False, sort_keys=True) - csvPF.WriteRowNoFilter(row) - elif GC.Values[GC.CSV_OUTPUT_USERS_AUDIT]: - csvPF.WriteRowNoFilter({'User': user}) - if csvPF: - csvPF.writeCSVfile(Ent.Plural(entityType)) - -# gam show tagmanageraccounts -# [includegoogletags []] -# [formatjson] -# gam print tagmanagerccounts [todrive *] -# [includegoogletags []] -# [formatjson [quotechar ]] -def printShowTagManagerAccounts(users): - printShowTagManagerObjects(users, Ent.TAGMANAGER_ACCOUNT) - -# gam show tagmanagercontainers -# [formatjson] -# gam print tagmanagercontainers [todrive *] -# [formatjson [quotechar ]] -def printShowTagManagerContainers(users): - printShowTagManagerObjects(users, Ent.TAGMANAGER_CONTAINER) - -# gam show tagmanagerworkspaces -# [formatjson] -# gam print tagmanagerworkspaces -# [formatjson [quotechar ]] -def printShowTagManagerWorkspaces(users): - printShowTagManagerObjects(users, Ent.TAGMANAGER_WORKSPACE) - -# gam show tagmanagertags -# [formatjson] -# gam print tagmanagertags [todrive *] -# [formatjson [quotechar ]] -def printShowTagManagerTags(users): - printShowTagManagerObjects(users, Ent.TAGMANAGER_TAG) - -# gam show tagmanagerpermissions -# [formatjson] -# gam print tagmanagerpermissions [todrive *] -# [formatjson [quotechar ]] -def printShowTagManagerPermissions(users): - printShowTagManagerObjects(users, Ent.TAGMANAGER_PERMISSION) - diff --git a/src/gam/cmd/users/display.py b/src/gam/cmd/users/display.py index 263b67e8..097c238d 100644 --- a/src/gam/cmd/users/display.py +++ b/src/gam/cmd/users/display.py @@ -10,7 +10,7 @@ import json from gam.cmd.users.manage import _filterSchemaFields, _filterUserMultiAttributes, _formatLanguagesList, _getUserMultiAttributeFilters, getUserLicenses from gam.util.schema_utils import _getSchemaNameList, _initSchemaParms -from gam.util.csv_pf import RI_JCOUNT, RI_ITEM +from gam.util.batch import RI_JCOUNT, RI_ITEM from gam.cmd.users.manage import ( USER_ADDRESSES_PROPERTY_PRINT_ORDER, @@ -68,7 +68,6 @@ from gam.util.access import accessErrorExit, checkEntityDNEorAccessErrorExit, en from gam.util.api import _getAdminEmail, buildGAPIObject, waitOnFailure from gam.util.api_call import callGAPI, callGAPIpages, checkGAPIError, yieldGAPIpages from gam.util.args import ( - formatLocalTime, getAddCSVData, getArgument, getBoolean, @@ -88,7 +87,6 @@ from gam.util.args import ( from gam.util.csv_pf import ( CSVPrintFile, FormatJSONQuoteChar, - batchRequestID, cleanJSON, flattenJSON, getFieldsFromFieldsList, @@ -113,7 +111,8 @@ from gam.util.entity import getCIGroupMembershipGraph, getEntityArgument, getEnt from gam.util.errors import entityActionFailedExit from gam.util.fileio import UNKNOWN from gam.util.orgunits import getOrgUnitItem -from gam.util.output import ERROR, executeBatch, writeStdout +from gam.util.batch import batchRequestID, executeBatch +from gam.util.output import ERROR, writeStdout, formatLocalTime from gam.util.group_parents import addJsonGroupParents, getGroupParents, showGroupParents from gam.cmd.resources import _getBuildingNameById from gam.util.domain_filters import getUserGroupDomainQueryFilters, initUserGroupDomainQueryFilters, makeUserGroupDomainQueryFilters diff --git a/src/gam/cmd/users/manage.py b/src/gam/cmd/users/manage.py index 474f3253..3411a52a 100644 --- a/src/gam/cmd/users/manage.py +++ b/src/gam/cmd/users/manage.py @@ -28,7 +28,6 @@ from gam.util.args import ( SORF_MSG_FILE_ARGUMENTS, checkArgumentPresent, checkForExtraneousArguments, - formatLocalTime, getArgument, getBoolean, getChoice, @@ -83,7 +82,7 @@ from gam.util.errors import ( from gam.util.fileio import closeFile, writeFile from gam.util.gdoc import openCSVFileReader from gam.util.orgunits import getOrgUnitItem -from gam.util.output import readStdin, setSysExitRC, systemErrorExit, writeStderr +from gam.util.output import readStdin, setSysExitRC, systemErrorExit, writeStderr, formatLocalTime from gam.constants import MULTIPLE_DELETED_USERS_FOUND_RC, NO_ENTITIES_FOUND_RC, PASSWORD_SAFE_CHARS, USER_SUSPENDED_RC from gam.constants import LICENSE_PRODUCT_SKUIDS from gam.util.tags import ( diff --git a/src/gam/cmd/userservices.py b/src/gam/cmd/userservices.py index 5c93739f..fdf4802f 100644 --- a/src/gam/cmd/userservices.py +++ b/src/gam/cmd/userservices.py @@ -2,7 +2,7 @@ import json -from gam.util.args import formatLocalTimestamp + from gam.cmd.calendar import ( CALENDAR_ACL_ROLES_MAP, @@ -33,7 +33,6 @@ from gam.util.args import ( YYYYMMDD_FORMAT, checkArgumentPresent, checkForExtraneousArguments, - formatLocalTime, getArgument, getBoolean, getCalendarReminder, @@ -49,7 +48,7 @@ from gam.util.args import ( normalizeEmailAddressOrUID, splitEmailAddress, ) -from gam.util.output import ISOformatTimeStamp +from gam.util.output import ISOformatTimeStamp, formatLocalTime, formatLocalTimestamp from gam.util.csv_pf import ( CSVPrintFile, FormatJSONQuoteChar, @@ -2176,15 +2175,3 @@ def printShowYouTubeChannel(users): csvPF.WriteRowNoFilter({'User': user}) if csvPF: csvPF.writeCSVfile('YouTube Channels') - -CORPORA_ALL_DRIVES = 'allDrives' -CORPORA_CHOICE_MAP = { - 'alldrives': CORPORA_ALL_DRIVES, - 'allshareddrives': CORPORA_ALL_DRIVES, - 'allteamdrives': CORPORA_ALL_DRIVES, - 'domain': 'domain', - 'onlyshareddrives': CORPORA_ALL_DRIVES, - 'onlyteamdrives': CORPORA_ALL_DRIVES, - 'user': 'user', - } - diff --git a/src/gam/cmd/youtube.py b/src/gam/cmd/youtube.py new file mode 100644 index 00000000..afba7e51 --- /dev/null +++ b/src/gam/cmd/youtube.py @@ -0,0 +1,148 @@ +"""GAM YouTube channel management.""" + +import json + +from gamlib import api as API +from gamlib import settings as GC +from gamlib import gapi as GAPI +from gam.var import Act, Cmd, Ent, Ind +from gam.util.svcacct import buildGAPIServiceObject +from gam.util.api_call import callGAPIpages +from gam.util.args import ( + BCP47_LANGUAGE_CODES_MAP, + addFieldToFieldsList, + getArgument, + getLanguageCode, + getString, +) +from gam.util.csv_pf import ( + CSVPrintFile, + FormatJSONQuoteChar, + cleanJSON, + flattenJSON, + getFieldsList, + showJSON, +) +from gam.util.display import ( + entityActionFailedWarning, + entityPerformActionNumItems, + printEntity, + printLine, + userYouTubeServiceNotEnabledWarning, +) +from gam.util.entity import getEntityArgument, getEntityList + +YOUTUBE_CHANNEL_FIELDS_CHOICE_MAP = { + 'brandingsettings': 'brandingSettings', + 'contentdetails': 'contentDetails', + 'contentownerdetails': 'contentOwnerDetails', + 'id': 'id', + 'localizations': 'localizations', + 'snippet': 'snippet', + 'statistics': 'statistics', + 'status': 'status', + 'topicdetails': 'topicDetails', + } + +YOUTUBE_CHANNEL_TIME_OBJECTS = {'publishedAt'} + +# gam show youtubechannels +# (mine| +# (ids|channels )| +# (forusername )| +# (managedbyme )) +# [languagecode ] +# [allfields|(fields )] +# [formatjson] +# gam print youtubechannels [todrive *] +# (mine| +# (ids|channels )| +# (forusername )| +# (managedbyme )) +# [languagecode ] +# [allfields|(fields )] +# [formatjson [quotechar ]] +def printShowYouTubeChannel(users): + csvPF = CSVPrintFile(['User', 'id'], 'sortall') if Act.csvFormat() else None + FJQC = FormatJSONQuoteChar(csvPF) + kwargs = {'mine': True} + languageCode = '' + fieldsList = ['id'] + while Cmd.ArgumentsRemaining(): + myarg = getArgument() + if csvPF and myarg == 'todrive': + csvPF.GetTodriveParameters() + elif myarg == 'mine': + kwargs = {'mine': True} + elif myarg in {'id', 'ids', 'channel', 'channels'}: + kwargs = {'id': ','.join(getEntityList(Cmd.OB_YOUTUBE_CHANNEL_ID_LIST))} + elif myarg == 'forusername': + kwargs = {'forUsername': getString(Cmd.OB_USER_NAME)} + elif myarg == 'managedbyme': + kwargs = {'managedByMe': True, 'onBehalfOfContentOwner': getString(Cmd.OB_USER_NAME)} + elif getFieldsList(myarg, YOUTUBE_CHANNEL_FIELDS_CHOICE_MAP, fieldsList): + pass + elif myarg == 'allfields': + for field in YOUTUBE_CHANNEL_FIELDS_CHOICE_MAP: + addFieldToFieldsList(field, YOUTUBE_CHANNEL_FIELDS_CHOICE_MAP, fieldsList) + elif myarg in {'languagecode', 'hl'}: + languageCode = getLanguageCode(BCP47_LANGUAGE_CODES_MAP) + else: + FJQC.GetFormatJSONQuoteChar(myarg, True) + kwargs['part'] = ','.join(set(fieldsList)) + if languageCode: + kwargs['hl'] = languageCode + i, count, users = getEntityArgument(users) + for user in users: + i += 1 + user, yt = buildGAPIServiceObject(API.YOUTUBE, user, i, count) + if not yt: + continue + try: + channels = callGAPIpages(yt.channels(), 'list', 'items', + throwReasons=GAPI.YOUTUBE_THROW_REASONS, + fields='nextPageToken,items', **kwargs) + except (GAPI.unsupportedSupervisedAccount, GAPI.unsupportedLanguageCode) as e: + entityActionFailedWarning([Ent.USER, user], str(e), i, count) + continue + except GAPI.contentOwnerAccountNotFound as e: + if 'managedByMe' in kwargs: + entityActionFailedWarning([Ent.USER, user, Ent.OWNER, kwargs['onBehalfOfContentOwner']], str(e), i, count) + else: + entityActionFailedWarning([Ent.USER, user], str(e), i, count) + continue + except (GAPI.serviceNotAvailable, GAPI.authError): + userYouTubeServiceNotEnabledWarning(user, i, count) + continue + if not csvPF: + jcount = len(channels) + if not FJQC.formatJSON: + entityPerformActionNumItems([Ent.USER, user], jcount, Ent.YOUTUBE_CHANNEL, i, count) + Ind.Increment() + j = 0 + for channel in channels: + j += 1 + if FJQC.formatJSON: + printLine(json.dumps(cleanJSON(channel, timeObjects=YOUTUBE_CHANNEL_TIME_OBJECTS), + ensure_ascii=False, sort_keys=True)) + break + printEntity([Ent.YOUTUBE_CHANNEL, channel['id']], j, jcount) + Ind.Increment() + showJSON(None, channel, skipObjects={'id'}, timeObjects=YOUTUBE_CHANNEL_TIME_OBJECTS) + Ind.Decrement() + Ind.Decrement() + elif channels: + for channel in channels: + row = {'User': user, 'id': channel['id']} + flattenJSON(channel, flattened=row, timeObjects=YOUTUBE_CHANNEL_TIME_OBJECTS) + if not FJQC.formatJSON: + csvPF.WriteRowTitles(row) + elif csvPF.CheckRowTitles(row): + row = {'User': user, 'id': channel['id'], + 'JSON': json.dumps(cleanJSON(channel, timeObjects=YOUTUBE_CHANNEL_TIME_OBJECTS), + ensure_ascii=False, sort_keys=True)} + csvPF.WriteRowNoFilter(row) + elif GC.Values[GC.CSV_OUTPUT_USERS_AUDIT]: + csvPF.WriteRowNoFilter({'User': user}) + if csvPF: + csvPF.writeCSVfile('YouTube Channels') diff --git a/src/gam/util/api.py b/src/gam/util/api.py index 11d4420f..34b0d6d0 100644 --- a/src/gam/util/api.py +++ b/src/gam/util/api.py @@ -40,17 +40,17 @@ from gamlib import settings as GC from gamlib import state as GM from gamlib import msgs as Msg from gamlib import yubikey -from gam.var import Ent, Ind +from gam.var import Ent from gam.constants import API_ACCESS_DENIED_RC, GOOGLE_API_ERROR_RC, NETWORK_ERROR_RC, NO_SCOPES_FOR_API_RC, REFRESH_EXPIRY, SOCKET_ERROR_RC, SYSTEM_ERROR_RC from util.args import UTF8, YYYYMMDDTHHMMSSZ_FORMAT -from util.display import SERVICE_NOT_APPLICABLE_RC, entityActionFailedWarning, printBlankLine, printKeyValueList, userServiceNotEnabledWarning +from util.display import SERVICE_NOT_APPLICABLE_RC, entityActionFailedWarning, printBlankLine, userServiceNotEnabledWarning from util.errors import INVALID_JSON_RC, OAUTH2SERVICE_JSON_REQUIRED_RC, OAUTH2_TXT_REQUIRED_RC, expiredRevokedOauth2TxtExit, invalidDiscoveryJsonExit, invalidOauth2TxtExit, invalidOauth2serviceJsonExit from util.fileio import FILE_ERROR_RC, UNKNOWN, incrAPICallsRetryData, readFile, writeFile -from util.output import flushStderr, setSysExitRC, stderrErrorMsg, systemErrorExit, writeStderr, writeStdout +from util.output import flushStderr, stderrErrorMsg, systemErrorExit, writeStderr, writeStdout HTML_TITLE_PATTERN = re.compile(r'.*(.+)') -from gam.constants import GAM_LATEST_RELEASE, GAM_USER_AGENT, __author__, __version__ +from gam.constants import GAM_USER_AGENT, __author__, __version__ # Constants only used in this module @@ -150,39 +150,6 @@ def transportCreateRequest(httpObj=None): httpObj = getHttpObj() return transportAgentRequest(httpObj) -def doGAMCheckForUpdates(forceCheck): - def _gamLatestVersionNotAvailable(): - if forceCheck: - systemErrorExit(NETWORK_ERROR_RC, Msg.GAM_LATEST_VERSION_NOT_AVAILABLE) - - try: - _, c = getHttpObj(timeout=10).request(GAM_LATEST_RELEASE, 'GET', headers={'Accept': 'application/vnd.github.v3.text+json'}) - try: - release_data = json.loads(c) - except (IndexError, KeyError, SyntaxError, TypeError, ValueError): - _gamLatestVersionNotAvailable() - return - if not isinstance(release_data, dict) or 'tag_name' not in release_data: - _gamLatestVersionNotAvailable() - return - current_version = __version__ - latest_version = release_data['tag_name'] - if latest_version[0].lower() == 'v': - latest_version = latest_version[1:] - printKeyValueList(['Version Check', None]) - Ind.Increment() - printKeyValueList(['Current', current_version]) - printKeyValueList([' Latest', latest_version]) - Ind.Decrement() - if forceCheck < 0: - setSysExitRC(1 if latest_version > current_version else 0) - return - except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, - google.auth.exceptions.TransportError, - RuntimeError, ConnectionError, OSError) as e: - if forceCheck: - handleServerError(e) - def get_adc_request(): request = google.auth.transport.requests.Request() if GM.Globals[GM.IS_ON_GCE]: diff --git a/src/gam/util/args.py b/src/gam/util/args.py index 0afcf4eb..8d259e2d 100644 --- a/src/gam/util/args.py +++ b/src/gam/util/args.py @@ -38,10 +38,7 @@ __all__ = [ 'checkArgumentPresent', 'checkDataField', 'checkForExtraneousArguments', 'checkGetArgument', 'checkMatchSkipFields', 'checkSubkeyField', 'encodeOrgUnitPath', 'escapeCRsNLs', - 'floatLimits', 'formatFileSize', 'formatHTTPError', - 'formatLocalDatestamp', 'formatLocalSecondsTimestamp', 'formatLocalTime', - 'formatLocalTimestamp', 'formatLocalTimestampUTC', - 'formatMaxMessageBytes', 'formatMilliSeconds', + 'floatLimits', 'formatHTTPError', 'getACLRoles', 'getAddCSVData', 'getAgeTime', 'getArgument', 'getArgumentEmptyAllowed', 'getBoolean', 'getCalendarReminder', 'getCharSet', 'getCharacter', @@ -527,26 +524,6 @@ def getLanguageCode(languageCodeMap): invalidChoiceExit(choice, languageCodeMap, False) missingChoiceExit(languageCodeMap) -def addCourseIdScope(courseId): - if not courseId.isdigit() and courseId[:2] not in {'d:', 'p:'}: - return f'd:{courseId}' - return courseId - -def removeCourseIdScope(courseId): - if courseId.startswith('d:'): - return courseId[2:] - return courseId - -def addCourseAliasScope(alias): - if alias[:2] not in {'d:', 'p:'}: - return f'd:{alias}' - return alias - -def removeCourseAliasScope(alias): - if alias.startswith('d:'): - return alias[2:] - return alias - def getCourseAlias(): if Cmd.ArgumentsRemaining(): courseAlias = Cmd.Current() @@ -1527,67 +1504,10 @@ def splitEmailAddress(emailAddress): return (emailAddress.lower(), GC.Values[GC.DOMAIN]) return (emailAddress[:atLoc].lower(), emailAddress[atLoc+1:].lower()) -def formatFileSize(size): - if size == 0: - return '0 KB' - if size < ONE_KILO_10_BYTES: - return '1 KB' - if size < ONE_MEGA_10_BYTES: - return f'{size/ONE_KILO_10_BYTES:.2f} KB' - if size < ONE_GIGA_10_BYTES: - return f'{size/ONE_MEGA_10_BYTES:.2f} MB' - if size < ONE_TERA_10_BYTES: - return f'{size/ONE_GIGA_10_BYTES:.2f} GB' - return f'{size/ONE_TERA_10_BYTES:.2f} TB' -def formatLocalTime(dateTimeStr): - if dateTimeStr in {NEVER_TIME, NEVER_TIME_NOMS}: - return GC.Values[GC.NEVER_TIME] - try: - timestamp = arrow.get(dateTimeStr) - if not GC.Values[GC.OUTPUT_TIMEFORMAT]: - if GM.Globals[GM.CONVERT_TO_LOCAL_TIME]: - return ISOformatTimeStamp(timestamp.astimezone(GC.Values[GC.TIMEZONE])) - return timestamp.strftime(YYYYMMDDTHHMMSSZ_FORMAT) - if GM.Globals[GM.CONVERT_TO_LOCAL_TIME]: - return timestamp.astimezone(GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) - return timestamp.strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) - except (arrow.parser.ParserError, OverflowError): - return dateTimeStr -def formatLocalSecondsTimestamp(timestamp): - if not GC.Values[GC.OUTPUT_TIMEFORMAT]: - return ISOformatTimeStamp(arrow.Arrow.fromtimestamp(int(timestamp), GC.Values[GC.TIMEZONE])) - return arrow.Arrow.fromtimestamp(int(timestamp), GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) -def formatLocalTimestamp(timestamp): - if not GC.Values[GC.OUTPUT_TIMEFORMAT]: - return ISOformatTimeStamp(arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE])) - return arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) -def formatLocalTimestampUTC(timestamp): - return ISOformatTimeStamp(arrow.Arrow.fromtimestamp(int(timestamp)//1000, 'UTC')) - -def formatLocalDatestamp(timestamp): - try: - if not GC.Values[GC.OUTPUT_DATEFORMAT]: - return arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE]).strftime(YYYYMMDD_FORMAT) - return arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_DATEFORMAT]) - except OverflowError: - return NEVER_DATE - -def formatMaxMessageBytes(maxMessageBytes, oneKiloBytes, oneMegaBytes): - if maxMessageBytes < oneKiloBytes: - return maxMessageBytes - if maxMessageBytes < oneMegaBytes: - return f'{maxMessageBytes//oneKiloBytes}K' - return f'{maxMessageBytes//oneMegaBytes}M' - -def formatMilliSeconds(millis): - seconds, millis = divmod(millis, 1000) - minutes, seconds = divmod(seconds, 60) - hours, minutes = divmod(minutes, 60) - return f'{hours:02d}:{minutes:02d}:{seconds:02d}' def getPhraseDNEorSNA(email): return Msg.DOES_NOT_EXIST if getEmailAddressDomain(email) == GC.Values[GC.DOMAIN] else Msg.SERVICE_NOT_APPLICABLE diff --git a/src/gam/util/batch.py b/src/gam/util/batch.py index 5c44a69a..d2b5540b 100644 --- a/src/gam/util/batch.py +++ b/src/gam/util/batch.py @@ -39,6 +39,26 @@ from gam.constants import GAM from gam.var import Cmd, Ind +def executeBatch(dbatch): + dbatch.execute() + if GC.Values[GC.INTER_BATCH_WAIT] > 0: + time.sleep(GC.Values[GC.INTER_BATCH_WAIT]) + +# Batch processing request_id fields +RI_ENTITY = 0 +RI_I = 1 +RI_COUNT = 2 +RI_J = 3 +RI_JCOUNT = 4 +RI_ITEM = 5 +RI_ROLE = 6 +RI_OPTION = 7 + +def batchRequestID(entityName, i, count, j, jcount, item, role=None, option=None): + if role is None and option is None: + return f'{entityName}\n{i}\n{count}\n{j}\n{jcount}\n{item}' + return f'{entityName}\n{i}\n{count}\n{j}\n{jcount}\n{item}\n{role}\n{option}' + class NullHandler(logging.Handler): diff --git a/src/gam/util/connection.py b/src/gam/util/connection.py index 4ce4b08d..04f7e6ec 100644 --- a/src/gam/util/connection.py +++ b/src/gam/util/connection.py @@ -1,5 +1,6 @@ """Network diagnostics, version display, and usage functions.""" +import json import os import platform import socket @@ -8,6 +9,7 @@ import struct import sys import arrow +import google.auth.exceptions import httplib2 from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -21,7 +23,7 @@ from gamlib import msgs as Msg from gamlib import verlibs from gam.constants import ( - FN_GAMCOMMANDS_TXT, GAM, GAM_URL, GAM_WIKI, + FN_GAMCOMMANDS_TXT, GAM, GAM_LATEST_RELEASE, GAM_URL, GAM_WIKI, GOOGLE_TIMECHECK_LOCATION, MAX_LOCAL_GOOGLE_TIME_OFFSET, NETWORK_ERROR_RC, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, ) @@ -31,11 +33,11 @@ from util.display import printBlankLine, printKeyValueList from util.errors import unknownArgumentExit from util.output import ( createGreenText, createRedText, createYellowText, - flushStdout, stderrWarningMsg, systemErrorExit, writeStdout, + flushStdout, setSysExitRC, stderrWarningMsg, systemErrorExit, writeStdout, ) -from util.api import doGAMCheckForUpdates, getHttpObj, getService, handleServerError, waitOnFailure +from util.api import getHttpObj, getService, handleServerError, waitOnFailure from gam.constants import GAM_USER_AGENT, __author__, __version__ -from gam.var import Cmd, Ent +from gam.var import Cmd, Ent, Ind # gam.__init__ attributes that can't be imported at module level # (connection.py is imported BY __init__.py during init) @@ -280,6 +282,39 @@ def doComment(): writeStdout(Cmd.QuotedArgumentList(Cmd.Remaining())+'\n') # gam version [check|checkrc|simple|extended] [timeoffset] [nooffseterror] [location ] +def doGAMCheckForUpdates(forceCheck): + def _gamLatestVersionNotAvailable(): + if forceCheck: + systemErrorExit(NETWORK_ERROR_RC, Msg.GAM_LATEST_VERSION_NOT_AVAILABLE) + + try: + _, c = getHttpObj(timeout=10).request(GAM_LATEST_RELEASE, 'GET', headers={'Accept': 'application/vnd.github.v3.text+json'}) + try: + release_data = json.loads(c) + except (IndexError, KeyError, SyntaxError, TypeError, ValueError): + _gamLatestVersionNotAvailable() + return + if not isinstance(release_data, dict) or 'tag_name' not in release_data: + _gamLatestVersionNotAvailable() + return + current_version = __version__ + latest_version = release_data['tag_name'] + if latest_version[0].lower() == 'v': + latest_version = latest_version[1:] + printKeyValueList(['Version Check', None]) + Ind.Increment() + printKeyValueList(['Current', current_version]) + printKeyValueList([' Latest', latest_version]) + Ind.Decrement() + if forceCheck < 0: + setSysExitRC(1 if latest_version > current_version else 0) + return + except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, + google.auth.exceptions.TransportError, + RuntimeError, ConnectionError, OSError) as e: + if forceCheck: + handleServerError(e) + def doVersion(checkForArgs=True): forceCheck = 0 extended = noOffsetError = timeOffset = simple = False diff --git a/src/gam/util/csv_pf.py b/src/gam/util/csv_pf.py index a59bda25..1c6f0290 100644 --- a/src/gam/util/csv_pf.py +++ b/src/gam/util/csv_pf.py @@ -23,13 +23,13 @@ from tempfile import TemporaryFile from util.api import _getAdminEmail, buildGAPIObject from util.svcacct import buildGAPIServiceObject, chooseSaAPI from util.api_call import callGAPI, callGAPIpages -from util.args import LOCALE_CODES_MAP, NEVER_TIME, TRUE_FALSE, UTF8, YYYYMMDD_FORMAT, YYYYMMDD_PATTERN, escapeCRsNLs, formatLocalTime, formatLocalTimestamp, getArgument, getBoolean, getCharacter, getChoice, getInteger, getLanguageCode, getSheetEntity, getSheetIdFromSheetEntity, getString, normalizeEmailAddressOrUID, protectedSheetId, todaysTime +from util.args import LOCALE_CODES_MAP, NEVER_TIME, TRUE_FALSE, UTF8, YYYYMMDD_FORMAT, YYYYMMDD_PATTERN, escapeCRsNLs, getArgument, getBoolean, getCharacter, getChoice, getInteger, getLanguageCode, getSheetEntity, getSheetIdFromSheetEntity, getString, normalizeEmailAddressOrUID, protectedSheetId, todaysTime from util.display import ACTION_FAILED_RC, entityActionFailedWarning, entityActionPerformed, printBlankLine, printJSONKey, printJSONValue, printKeyValueList, userDriveServiceNotEnabledWarning from util.email import send_email from util.entity import MIMETYPE_GA_FOLDER, MIMETYPE_GA_SPREADSHEET, checkUserExists, getEntityArgument from util.errors import USAGE_ERROR_RC, invalidArgumentExit, invalidChoiceExit, missingArgumentExit, unknownArgumentExit, usageErrorExit from util.fileio import FILE_ERROR_RC, StringIOobject, closeFile, fdErrorMessage, openFile -from util.output import ISOformatTimeStamp, WARNING, currentCountNL, formatKeyValueList, printWarningMessage, setSysExitRC, stderrErrorMsg, stderrWarningMsg, systemErrorExit, writeStdout +from util.output import ISOformatTimeStamp, WARNING, currentCountNL, formatKeyValueList, formatLocalTime, formatLocalTimestamp, printWarningMessage, setSysExitRC, stderrErrorMsg, stderrWarningMsg, systemErrorExit, writeStdout from gam.var import Act, Cmd, Ent, Ind @@ -1975,18 +1975,3 @@ class FormatJSONQuoteChar: if noExit: return False unknownArgumentExit() - -# Batch processing request_id fields -RI_ENTITY = 0 -RI_I = 1 -RI_COUNT = 2 -RI_J = 3 -RI_JCOUNT = 4 -RI_ITEM = 5 -RI_ROLE = 6 -RI_OPTION = 7 - -def batchRequestID(entityName, i, count, j, jcount, item, role=None, option=None): - if role is None and option is None: - return f'{entityName}\n{i}\n{count}\n{j}\n{jcount}\n{item}' - return f'{entityName}\n{i}\n{count}\n{j}\n{jcount}\n{item}\n{role}\n{option}' diff --git a/src/gam/util/entity.py b/src/gam/util/entity.py index a9f8ffd0..73ee4ac2 100644 --- a/src/gam/util/entity.py +++ b/src/gam/util/entity.py @@ -126,7 +126,7 @@ from util.args import shlexSplitList, shlexSplitListStatus # noqa: E402,F401 - from util.uid import convertUIDtoEmailAddress, convertUIDtoEmailAddressWithType, convertEmailAddressToUID # noqa: F401 - re-export from gam.constants import DATA_ERROR_RC, INVALID_ENTITY_RC, NO_ENTITIES_FOUND_RC, UNKNOWN_ERROR_RC from gamlib import skus as SKU -from util.args import ARCHIVED_ARGUMENTS, FALSE_VALUES, SUSPENDED_ARGUMENTS, TRUE_VALUES, _getIsArchived, _getIsSuspended, checkArgumentPresent, checkDataField, checkMatchSkipFields, checkSubkeyField, getArgument, getCharSet, getChoice, getDelimiter, getMatchSkipFields, getREPattern, getString, makeOrgUnitPathAbsolute, normalizeEmailAddressOrUID, orgUnitPathQuery, removeCourseIdScope, splitEmailAddress, validateEmailAddressOrUID +from util.args import ARCHIVED_ARGUMENTS, FALSE_VALUES, SUSPENDED_ARGUMENTS, TRUE_VALUES, _getIsArchived, _getIsSuspended, checkArgumentPresent, checkDataField, checkMatchSkipFields, checkSubkeyField, getArgument, getCharSet, getChoice, getDelimiter, getMatchSkipFields, getREPattern, getString, makeOrgUnitPathAbsolute, normalizeEmailAddressOrUID, orgUnitPathQuery, splitEmailAddress, validateEmailAddressOrUID from util.display import ENTITY_DOES_NOT_EXIST_RC, entityActionFailedWarning, entityActionNotPerformedWarning, entityDoesNotExistWarning, entityPerformActionNumItems, getPageMessage, getPageMessageForWhom, printGettingAllAccountEntities, printGettingAllEntityItemsForWhom, printGotEntityItemsForWhom, setGettingAllEntityItemsForWhom from util.errors import csvDataAlreadySavedErrorExit, csvFieldErrorExit, invalidArgumentExit, invalidChoiceExit, missingArgumentExit, usageErrorExit from util.fileio import closeFile, openFile, setFilePath @@ -1575,3 +1575,23 @@ SHAREDDRIVE_QUERY_SHORTCUTS_MAP = { 'allitems': 'allitems', } +def addCourseIdScope(courseId): + if not courseId.isdigit() and courseId[:2] not in {'d:', 'p:'}: + return f'd:{courseId}' + return courseId + +def removeCourseIdScope(courseId): + if courseId.startswith('d:'): + return courseId[2:] + return courseId + +def addCourseAliasScope(alias): + if alias[:2] not in {'d:', 'p:'}: + return f'd:{alias}' + return alias + +def removeCourseAliasScope(alias): + if alias.startswith('d:'): + return alias[2:] + return alias + diff --git a/src/gam/util/output.py b/src/gam/util/output.py index eef79338..6303c0bf 100644 --- a/src/gam/util/output.py +++ b/src/gam/util/output.py @@ -129,11 +129,6 @@ def createYellowText(text): """Uses ANSI encoding to create yellow text if supported.""" return createColoredText(text, '\u001b[33m') -def executeBatch(dbatch): - dbatch.execute() - if GC.Values[GC.INTER_BATCH_WAIT] > 0: - time.sleep(GC.Values[GC.INTER_BATCH_WAIT]) - def _stripControlCharsFromName(name): for cc in ['\x00', '\r', '\n']: name = name.replace(cc, '') @@ -213,3 +208,71 @@ def currentISOformatTimeStamp(timespec='milliseconds'): return arrow.now(GC.Values[GC.TIMEZONE]).isoformat('T', timespec) +from gam.constants import ONE_KILO_10_BYTES, ONE_MEGA_10_BYTES, ONE_GIGA_10_BYTES, ONE_TERA_10_BYTES + +def formatFileSize(size): + if size == 0: + return '0 KB' + if size < ONE_KILO_10_BYTES: + return '1 KB' + if size < ONE_MEGA_10_BYTES: + return f'{size/ONE_KILO_10_BYTES:.2f} KB' + if size < ONE_GIGA_10_BYTES: + return f'{size/ONE_MEGA_10_BYTES:.2f} MB' + if size < ONE_TERA_10_BYTES: + return f'{size/ONE_GIGA_10_BYTES:.2f} GB' + return f'{size/ONE_TERA_10_BYTES:.2f} TB' + +def formatMaxMessageBytes(maxMessageBytes, oneKiloBytes, oneMegaBytes): + if maxMessageBytes < oneKiloBytes: + return maxMessageBytes + if maxMessageBytes < oneMegaBytes: + return f'{maxMessageBytes//oneKiloBytes}K' + return f'{maxMessageBytes//oneMegaBytes}M' + +def formatMilliSeconds(millis): + seconds, millis = divmod(millis, 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + return f'{hours:02d}:{minutes:02d}:{seconds:02d}' + +from gam.constants import NEVER_TIME, NEVER_TIME_NOMS +YYYYMMDDTHHMMSSZ_FORMAT = '%Y-%m-%dT%H:%M:%SZ' +YYYYMMDD_FORMAT = '%Y-%m-%d' +NEVER_DATE = '1970-01-01' + +def formatLocalTime(dateTimeStr): + if dateTimeStr in {NEVER_TIME, NEVER_TIME_NOMS}: + return GC.Values[GC.NEVER_TIME] + try: + timestamp = arrow.get(dateTimeStr) + if not GC.Values[GC.OUTPUT_TIMEFORMAT]: + if GM.Globals[GM.CONVERT_TO_LOCAL_TIME]: + return ISOformatTimeStamp(timestamp.astimezone(GC.Values[GC.TIMEZONE])) + return timestamp.strftime(YYYYMMDDTHHMMSSZ_FORMAT) + if GM.Globals[GM.CONVERT_TO_LOCAL_TIME]: + return timestamp.astimezone(GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) + return timestamp.strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) + except (arrow.parser.ParserError, OverflowError): + return dateTimeStr + +def formatLocalSecondsTimestamp(timestamp): + if not GC.Values[GC.OUTPUT_TIMEFORMAT]: + return ISOformatTimeStamp(arrow.Arrow.fromtimestamp(int(timestamp), GC.Values[GC.TIMEZONE])) + return arrow.Arrow.fromtimestamp(int(timestamp), GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) + +def formatLocalTimestamp(timestamp): + if not GC.Values[GC.OUTPUT_TIMEFORMAT]: + return ISOformatTimeStamp(arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE])) + return arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_TIMEFORMAT]) + +def formatLocalTimestampUTC(timestamp): + return ISOformatTimeStamp(arrow.Arrow.fromtimestamp(int(timestamp)//1000, 'UTC')) + +def formatLocalDatestamp(timestamp): + try: + if not GC.Values[GC.OUTPUT_DATEFORMAT]: + return arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE]).strftime(YYYYMMDD_FORMAT) + return arrow.Arrow.fromtimestamp(int(timestamp)//1000, GC.Values[GC.TIMEZONE]).strftime(GC.Values[GC.OUTPUT_DATEFORMAT]) + except OverflowError: + return NEVER_DATE diff --git a/tests/test_args.py b/tests/test_args.py index 28f8bdce..4894a13a 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -8,41 +8,41 @@ class TestCourseScopes: """Test course ID and alias scope manipulation.""" def test_add_course_id_scope_plain(self): - from gam.util.args import addCourseIdScope + from gam.util.entity import addCourseIdScope assert addCourseIdScope('12345') == '12345' # numeric, no prefix def test_add_course_id_scope_name(self): - from gam.util.args import addCourseIdScope + from gam.util.entity import addCourseIdScope assert addCourseIdScope('my-course') == 'd:my-course' def test_add_course_id_scope_already_prefixed(self): - from gam.util.args import addCourseIdScope + from gam.util.entity import addCourseIdScope assert addCourseIdScope('d:my-course') == 'd:my-course' assert addCourseIdScope('p:my-course') == 'p:my-course' def test_remove_course_id_scope(self): - from gam.util.args import removeCourseIdScope + from gam.util.entity import removeCourseIdScope assert removeCourseIdScope('d:my-course') == 'my-course' def test_remove_course_id_scope_no_prefix(self): - from gam.util.args import removeCourseIdScope + from gam.util.entity import removeCourseIdScope assert removeCourseIdScope('12345') == '12345' def test_add_course_alias_scope(self): - from gam.util.args import addCourseAliasScope + from gam.util.entity import addCourseAliasScope assert addCourseAliasScope('my-alias') == 'd:my-alias' def test_add_course_alias_scope_already_prefixed(self): - from gam.util.args import addCourseAliasScope + from gam.util.entity import addCourseAliasScope assert addCourseAliasScope('d:my-alias') == 'd:my-alias' assert addCourseAliasScope('p:my-alias') == 'p:my-alias' def test_remove_course_alias_scope(self): - from gam.util.args import removeCourseAliasScope + from gam.util.entity import removeCourseAliasScope assert removeCourseAliasScope('d:my-alias') == 'my-alias' def test_remove_course_alias_scope_no_prefix(self): - from gam.util.args import removeCourseAliasScope + from gam.util.entity import removeCourseAliasScope assert removeCourseAliasScope('my-alias') == 'my-alias' @@ -103,7 +103,7 @@ class TestFormatting: """Test formatting helper functions.""" def test_format_milliseconds(self): - from gam.util.args import formatMilliSeconds + from gam.util.output import formatMilliSeconds assert formatMilliSeconds(0) == '00:00:00' assert formatMilliSeconds(1000) == '00:00:01' assert formatMilliSeconds(61000) == '00:01:01' @@ -115,23 +115,23 @@ class TestFormatting: assert result == '404: Not Found - Resource missing' def test_format_max_message_bytes_raw(self): - from gam.util.args import formatMaxMessageBytes + from gam.util.output import formatMaxMessageBytes assert formatMaxMessageBytes(500, 1024, 1048576) == 500 def test_format_max_message_bytes_kilobytes(self): - from gam.util.args import formatMaxMessageBytes + from gam.util.output import formatMaxMessageBytes assert formatMaxMessageBytes(2048, 1024, 1048576) == '2K' def test_format_max_message_bytes_megabytes(self): - from gam.util.args import formatMaxMessageBytes + from gam.util.output import formatMaxMessageBytes assert formatMaxMessageBytes(5242880, 1024, 1048576) == '5M' def test_format_file_size_zero(self): - from gam.util.args import formatFileSize + from gam.util.output import formatFileSize assert formatFileSize(0) == '0 KB' def test_format_file_size_small(self): - from gam.util.args import formatFileSize + from gam.util.output import formatFileSize assert formatFileSize(500) == '1 KB'