gam7 7.9.1__py3-none-any.whl → 7.9.3__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.09.01'
28
+ __version__ = '7.09.03'
29
29
  __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
30
30
 
31
31
  #pylint: disable=wrong-import-position
@@ -25947,7 +25947,7 @@ def exitIfChatNotConfigured(chat, kvList, errMsg, i, count):
25947
25947
  if (('No bot associated with this project.' in errMsg) or
25948
25948
  ('Invalid project number.' in errMsg) or
25949
25949
  ('Google Chat app not found.' in errMsg)):
25950
- systemErrorExit(API_ACCESS_DENIED_RC, Msg.TO_SET_UP_GOOGLE_CHAT.format(setupChatURL(chat)))
25950
+ systemErrorExit(API_ACCESS_DENIED_RC, Msg.TO_SET_UP_GOOGLE_CHAT.format(setupChatURL(chat), GM.Globals[GM.OAUTH2SERVICE_JSON_DATA]['project_id']))
25951
25951
  entityActionFailedWarning(kvList, errMsg, i, count)
25952
25952
 
25953
25953
  def _getChatAdminAccess(adminAPI, userAPI):
@@ -28119,7 +28119,9 @@ def simplifyChromeSchema(schema):
28119
28119
  }
28120
28120
  fieldDescriptions = schema['fieldDescriptions']
28121
28121
  savedSettingName = ''
28122
+ savedTypeName = ''
28122
28123
  for mtype in schema['definition']['messageType']:
28124
+ numSettings = len(mtype['field'])
28123
28125
  for setting in mtype['field']:
28124
28126
  setting_name = setting['name']
28125
28127
  setting_dict = {'name': setting_name,
@@ -28127,9 +28129,9 @@ def simplifyChromeSchema(schema):
28127
28129
  'descriptions': [],
28128
28130
  'type': setting['type'],
28129
28131
  }
28130
- if setting_dict['type'] == 'TYPE_INT64' and savedSettingName:
28132
+ if mtype['name'] == savedTypeName and numSettings == 1:
28131
28133
  setting_dict['name'] = savedSettingName
28132
- savedSettingName = ''
28134
+ savedTypeName = ''
28133
28135
  if setting_dict['type'] == 'TYPE_STRING' and setting.get('label') == 'LABEL_REPEATED':
28134
28136
  setting_dict['type'] = 'TYPE_LIST'
28135
28137
  if setting_dict['type'] == 'TYPE_ENUM':
@@ -28152,6 +28154,7 @@ def simplifyChromeSchema(schema):
28152
28154
  break
28153
28155
  elif setting_dict['type'] == 'TYPE_MESSAGE':
28154
28156
  savedSettingName = setting_name
28157
+ savedTypeName = setting['typeName']
28155
28158
  continue
28156
28159
  else:
28157
28160
  setting_dict['enums'] = None
@@ -28310,7 +28313,16 @@ CHROME_SCHEMA_SPECIAL_CASES = {
28310
28313
  'chrome.users.RelaunchNotificationWithDurationV2':
28311
28314
  {'relaunchnotificationperiodduration':
28312
28315
  {'casedField': 'relaunchNotificationPeriodDuration',
28313
- 'type': 'duration', 'minVal': -1, 'maxVal': None}},
28316
+ 'type': 'duration', 'minVal': 1, 'maxVal': 168},
28317
+ 'relaunchinitialquietperiodduration':
28318
+ {'casedField': 'relaunchInitialQuietPeriodDuration',
28319
+ 'type': 'duration', 'minVal': 0, 'maxVal': None},
28320
+ 'relaunchwindowstarttime':
28321
+ {'casedField': 'relaunchWindowStartTime',
28322
+ 'type': 'timeOfDay'},
28323
+ 'relaunchwindowdurationmin':
28324
+ {'casedField': 'relaunchWindowDurationMin',
28325
+ 'type': 'duration', 'minVal': 1, 'maxVal': 1440}},
28314
28326
  'chrome.users.SecurityTokenSessionSettingsV2':
28315
28327
  {'securitytokensessionnotificationseconds':
28316
28328
  {'casedField': 'securityTokenSessionNotificationSeconds',
@@ -28472,7 +28484,7 @@ def doUpdateChromePolicy():
28472
28484
  return value
28473
28485
  #if vtype == timeOfDay:
28474
28486
  hours, minutes = value.split(':')
28475
- return {vtype: {'hours': hours, 'minutes': minutes}}
28487
+ return {vtype: {'hours': int(hours), 'minutes': int(minutes)}}
28476
28488
 
28477
28489
  cp = buildGAPIObject(API.CHROMEPOLICY)
28478
28490
  cd = buildGAPIObject(API.DIRECTORY)
@@ -28928,7 +28940,7 @@ def _showChromePolicySchema(schema, FJQC, i=0, count=0):
28928
28940
  return
28929
28941
  printEntity([Ent.CHROME_POLICY_SCHEMA, schema['name']], i, count)
28930
28942
  Ind.Increment()
28931
- showJSON(None, schema)
28943
+ showJSON(None, schema, dictObjectsKey={'messageType': 'name', 'field': 'name', 'fieldDescriptions': 'field'})
28932
28944
  Ind.Decrement()
28933
28945
 
28934
28946
  CHROME_POLICY_SCHEMA_FIELDS_CHOICE_MAP = {
@@ -28951,6 +28963,9 @@ CHROME_POLICY_SCHEMA_FIELDS_CHOICE_MAP = {
28951
28963
  # [formatjson]
28952
28964
  def doInfoChromePolicySchemas():
28953
28965
  cp = buildGAPIObject(API.CHROMEPOLICY)
28966
+ if checkArgumentPresent('std'):
28967
+ doInfoChromePolicySchemasStd(cp)
28968
+ return
28954
28969
  FJQC = FormatJSONQuoteChar()
28955
28970
  fieldsList = []
28956
28971
  name = _getChromePolicySchemaName()
@@ -28979,7 +28994,7 @@ def doInfoChromePolicySchemas():
28979
28994
  # [filter <String>]
28980
28995
  # <ChromePolicySchemaFieldName>* [fields <ChromePolicySchemaFieldNameList>]
28981
28996
  # [[formatjson [quotechar <Character>]]
28982
- def doPrintShowChromeSchemas():
28997
+ def doPrintShowChromePolicySchemas():
28983
28998
  def _printChromePolicySchema(schema):
28984
28999
  row = flattenJSON(schema)
28985
29000
  if not FJQC.formatJSON:
@@ -28993,10 +29008,12 @@ def doPrintShowChromeSchemas():
28993
29008
  row['JSON'] = json.dumps(cleanJSON(schema), ensure_ascii=False, sort_keys=True)
28994
29009
  csvPF.WriteRowNoFilter(row)
28995
29010
 
28996
- if checkArgumentPresent('std'):
28997
- doShowChromeSchemasStd()
28998
- return
28999
29011
  cp = buildGAPIObject(API.CHROMEPOLICY)
29012
+ if checkArgumentPresent('std'):
29013
+ if not Act.csvFormat():
29014
+ doShowChromePolicySchemasStd(cp)
29015
+ return
29016
+ unknownArgumentExit()
29000
29017
  parent = _getCustomersCustomerIdWithC()
29001
29018
  csvPF = CSVPrintFile(['name', 'schemaName', 'policyDescription',
29002
29019
  'policyApiLifecycle.policyApiLifecycleStage',
@@ -29056,9 +29073,51 @@ def doPrintShowChromeSchemas():
29056
29073
  if csvPF:
29057
29074
  csvPF.writeCSVfile('Chrome Policy Schemas')
29058
29075
 
29076
+ def _showChromePolicySchemaStd(schema):
29077
+ printKeyValueList([f'{schema.get("name")}', f'{schema.get("description")}'])
29078
+ Ind.Increment()
29079
+ for val in schema['settings'].values():
29080
+ vtype = val.get('type')
29081
+ printKeyValueList([f'{val.get("name")}', f'{vtype}'])
29082
+ Ind.Increment()
29083
+ if vtype == 'TYPE_ENUM':
29084
+ enums = val.get('enums', [])
29085
+ descriptions = val.get('descriptions', [])
29086
+ for i in range(len(val.get('enums', []))):
29087
+ printKeyValueList([f'{enums[i]}', f'{descriptions[i]}'])
29088
+ elif vtype == 'TYPE_BOOL':
29089
+ pvs = val.get('descriptions')
29090
+ for pvi in pvs:
29091
+ if isinstance(pvi, dict):
29092
+ pvalue = pvi.get('value')
29093
+ pdescription = pvi.get('description')
29094
+ printKeyValueList([f'{pvalue}', f'{pdescription}'])
29095
+ elif isinstance(pvi, list):
29096
+ printKeyValueList([f'{pvi[0]}'])
29097
+ else:
29098
+ description = val.get('descriptions')
29099
+ if len(description) > 0:
29100
+ printKeyValueList([f'{description[0]}'])
29101
+ Ind.Decrement()
29102
+ Ind.Decrement()
29103
+
29104
+ # gam info chromeschema std <SchemaName>
29105
+ def doInfoChromePolicySchemasStd(cp):
29106
+ name = _getChromePolicySchemaName()
29107
+ checkForExtraneousArguments()
29108
+ try:
29109
+ schema = callGAPI(cp.customers().policySchemas(), 'get',
29110
+ throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN],
29111
+ name=name)
29112
+ _, schema_dict = simplifyChromeSchema(schema)
29113
+ _showChromePolicySchemaStd(schema_dict)
29114
+ except GAPI.notFound:
29115
+ entityUnknownWarning(Ent.CHROME_POLICY_SCHEMA, name)
29116
+ except (GAPI.badRequest, GAPI.forbidden):
29117
+ accessErrorExit(None)
29118
+
29059
29119
  # gam show chromeschemas std [filter <String>]
29060
- def doShowChromeSchemasStd():
29061
- cp = buildGAPIObject(API.CHROMEPOLICY)
29120
+ def doShowChromePolicySchemasStd(cp):
29062
29121
  sfilter = None
29063
29122
  while Cmd.ArgumentsRemaining():
29064
29123
  myarg = getArgument()
@@ -29074,33 +29133,8 @@ def doShowChromeSchemasStd():
29074
29133
  for schema in result:
29075
29134
  schema_name, schema_dict = simplifyChromeSchema(schema)
29076
29135
  schemas[schema_name.lower()] = schema_dict
29077
- for _, value in sorted(iter(schemas.items())):
29078
- printKeyValueList([f'{value.get("name")}', f'{value.get("description")}'])
29079
- Ind.Increment()
29080
- for val in value['settings'].values():
29081
- vtype = val.get('type')
29082
- printKeyValueList([f'{val.get("name")}', f'{vtype}'])
29083
- Ind.Increment()
29084
- if vtype == 'TYPE_ENUM':
29085
- enums = val.get('enums', [])
29086
- descriptions = val.get('descriptions', [])
29087
- for i in range(len(val.get('enums', []))):
29088
- printKeyValueList([f'{enums[i]}', f'{descriptions[i]}'])
29089
- elif vtype == 'TYPE_BOOL':
29090
- pvs = val.get('descriptions')
29091
- for pvi in pvs:
29092
- if isinstance(pvi, dict):
29093
- pvalue = pvi.get('value')
29094
- pdescription = pvi.get('description')
29095
- printKeyValueList([f'{pvalue}', f'{pdescription}'])
29096
- elif isinstance(pvi, list):
29097
- printKeyValueList([f'{pvi[0]}'])
29098
- else:
29099
- description = val.get('descriptions')
29100
- if len(description) > 0:
29101
- printKeyValueList([f'{description[0]}'])
29102
- Ind.Decrement()
29103
- Ind.Decrement()
29136
+ for _, schema in sorted(iter(schemas.items())):
29137
+ _showChromePolicySchemaStd(schema)
29104
29138
  printBlankLine()
29105
29139
 
29106
29140
  # gam create chromenetwork
@@ -51561,6 +51595,9 @@ def getStatusEventDateTime(dateType, dateList):
51561
51595
  if dateType == 'timerange':
51562
51596
  startTime = getTimeOrDeltaFromNow(returnDateTime=True)[0]
51563
51597
  endTime = getTimeOrDeltaFromNow(returnDateTime=True)[0]
51598
+ if startTime >= endTime:
51599
+ Cmd.Backup()
51600
+ usageErrorExit(Msg.INVALID_EVENT_TIMERANGE.format(dateType, startTime, endTime))
51564
51601
  recurrence = []
51565
51602
  while checkArgumentPresent(['recurrence']):
51566
51603
  recurrence.append(getString(Cmd.OB_RECURRENCE))
@@ -76137,7 +76174,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
76137
76174
  Cmd.ARG_CHROMENEEDSATTN: doPrintShowChromeNeedsAttn,
76138
76175
  Cmd.ARG_CHROMEPOLICY: doPrintShowChromePolicies,
76139
76176
  Cmd.ARG_CHROMEPROFILE: doPrintShowChromeProfiles,
76140
- Cmd.ARG_CHROMESCHEMA: doPrintShowChromeSchemas,
76177
+ Cmd.ARG_CHROMESCHEMA: doPrintShowChromePolicySchemas,
76141
76178
  Cmd.ARG_CHROMESNVALIDITY: doPrintChromeSnValidity,
76142
76179
  Cmd.ARG_CHROMEVERSIONS: doPrintShowChromeVersions,
76143
76180
  Cmd.ARG_CIGROUP: doPrintCIGroups,
@@ -76269,7 +76306,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
76269
76306
  Cmd.ARG_CHROMENEEDSATTN: doPrintShowChromeNeedsAttn,
76270
76307
  Cmd.ARG_CHROMEPOLICY: doPrintShowChromePolicies,
76271
76308
  Cmd.ARG_CHROMEPROFILE: doPrintShowChromeProfiles,
76272
- Cmd.ARG_CHROMESCHEMA: doPrintShowChromeSchemas,
76309
+ Cmd.ARG_CHROMESCHEMA: doPrintShowChromePolicySchemas,
76273
76310
  Cmd.ARG_CHROMEVERSIONS: doPrintShowChromeVersions,
76274
76311
  Cmd.ARG_CIGROUPMEMBERS: doShowCIGroupMembers,
76275
76312
  Cmd.ARG_CIPOLICY: doPrintShowCIPolicies,
gam/gamlib/glcfg.py CHANGED
@@ -379,7 +379,7 @@ Defaults = {
379
379
  DEVICE_MAX_RESULTS: '200',
380
380
  DOMAIN: '',
381
381
  DRIVE_DIR: '',
382
- ENFORCE_EXPANSIVE_ACCESS: FALSE,
382
+ ENFORCE_EXPANSIVE_ACCESS: TRUE,
383
383
  DRIVE_MAX_RESULTS: '1000',
384
384
  DRIVE_V3_BETA: FALSE,
385
385
  DRIVE_V3_NATIVE_NAMES: TRUE,
gam/gamlib/glentity.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- # Copyright (C) 2024 Ross Scroggs All Rights Reserved.
3
+ # Copyright (C) 2025 Ross Scroggs All Rights Reserved.
4
4
  #
5
5
  # All Rights Reserved.
6
6
  #
gam/gamlib/glmsgs.py CHANGED
@@ -310,6 +310,7 @@ INVALID_ATTENDEE_CHANGE = 'Invalid attendee change "{0}"'
310
310
  INVALID_CHARSET = 'Invalid charset "{0}"'
311
311
  INVALID_DATE_TIME_RANGE = '{0} {1} must be greater than/equal to {2} {3}'
312
312
  INVALID_ENTITY = 'Invalid {0}, {1}'
313
+ INVALID_EVENT_TIMERANGE = '{0} {1} must be less than {2}'
313
314
  INVALID_FILE_SELECTION_WITH_ADMIN_ACCESS = 'Invalid file selection with adminaccess|asadmin'
314
315
  INVALID_GROUP = 'Invalid Group'
315
316
  INVALID_HTTP_HEADER = 'Invalid http header data: {0}'
gam/gamlib/glskus.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- # Copyright (C) 2023 Ross Scroggs All Rights Reserved.
3
+ # Copyright (C) 2025 Ross Scroggs All Rights Reserved.
4
4
  #
5
5
  # All Rights Reserved.
6
6
  #
gam/gamlib/glverlibs.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- # Copyright (C) 2023 Ross Scroggs All Rights Reserved.
3
+ # Copyright (C) 2025 Ross Scroggs All Rights Reserved.
4
4
  #
5
5
  # All Rights Reserved.
6
6
  #
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gam7
3
- Version: 7.9.1
3
+ Version: 7.9.3
4
4
  Summary: CLI tool to manage Google Workspace
5
5
  Project-URL: Homepage, https://github.com/GAM-team/GAM
6
6
  Project-URL: Issues, https://github.com/GAM-team/GAM/issues
@@ -1,4 +1,4 @@
1
- gam/__init__.py,sha256=EqqD1dpkkwSbBFUVS-L2B4ud6trEg59VU3z24ADlvj0,3517246
1
+ gam/__init__.py,sha256=M7CO7id7ZQuWK3T8Z9ROX3wtwlNWfCGVzh220rwexYU,3518789
2
2
  gam/__main__.py,sha256=amz0-959ph6zkZKqjaar4n60yho-T37w6qWI36qx0CA,1049
3
3
  gam/cacerts.pem,sha256=82Ak7btW_2XvocLUvAwPmpx8Chi0oqtZUG1gseLK_t4,50235
4
4
  gam/cbcm-v1.1beta1.json,sha256=xO5XloCQQULmPbFBx5bckdqmbLFQ7sJ2TImhE1ysDIY,19439
@@ -24,17 +24,17 @@ gam/atom/url.py,sha256=pxO1TlORxyKQTQ1bkBE1unFzjnv9c8LjJkm-UEORShY,4276
24
24
  gam/gamlib/__init__.py,sha256=z5mF-y0j8pm-YNFBaiuxB4M_GAUPG-cXWwrhYwrVReM,679
25
25
  gam/gamlib/glaction.py,sha256=1Il_HrChVnPkzZwiZs5au4mFQVtq4K1Z42uIuR6qdnI,9419
26
26
  gam/gamlib/glapi.py,sha256=7epQ2Hk9SK4Q6SImdJfvGvT5txftyfb5_jrF5K9cMpk,34253
27
- gam/gamlib/glcfg.py,sha256=moWpsBv5iaASEvI1VfiQiI85jUjX6tYXa1uD1Oz3Y5M,28297
27
+ gam/gamlib/glcfg.py,sha256=bNTckxzIM_HruxO2DfYsDbEgqOIz1RX6CbU6XOQQQyg,28296
28
28
  gam/gamlib/glclargs.py,sha256=zB6GMmDR_y5IRj7LuoM6-Bk_ETfNQ6sTqCNB6RdPfyg,42350
29
- gam/gamlib/glentity.py,sha256=fqWUlxQqPKlfFrkuPjCK2lZhhFBIZboCuO0qCxuEwqA,33691
29
+ gam/gamlib/glentity.py,sha256=6zL9uIMTXB4MwRRYKnCuAj7nZGfc16b-PIeeq4PGBlA,33691
30
30
  gam/gamlib/glgapi.py,sha256=cGtvFvvBU3XQJLgtLCjs_V5VBUem_k0b5uUrK21VmeA,39491
31
31
  gam/gamlib/glgdata.py,sha256=weRppttWm6uRyqtBoGPKoHiNZ2h28nhfUV4J_mbCszY,2707
32
32
  gam/gamlib/glglobals.py,sha256=J0xcHggVrUBzHJ5GruenKV-qV1zPKcK2qWgAgN3i5Jw,9608
33
33
  gam/gamlib/glindent.py,sha256=RfBa2LDfLIqPLL5vMfC689TCVmqn8xf-qulSzkiatrc,1228
34
- gam/gamlib/glmsgs.py,sha256=jiu6jRSspnbF25IbaTOV1NzvtCZlE-5ltfntitH2V5k,33677
35
- gam/gamlib/glskus.py,sha256=xJ1E2BZ_CGHN6I19c9i8DApb5bT5VT-hGyMEmQ5hSRY,15241
34
+ gam/gamlib/glmsgs.py,sha256=Ql1ejqGqZ7SFzNtejn4hd1O5YtBhELwgjfDVXE0qsJM,33735
35
+ gam/gamlib/glskus.py,sha256=Q4UfVnZZxKXpyBc-iQy_uBgbNyIL519jMXHBotrMPio,15241
36
36
  gam/gamlib/gluprop.py,sha256=IyPLCyvn7-NHTUenM71YPQPXRZXx6CB5q-GtJ-FYd1c,11461
37
- gam/gamlib/glverlibs.py,sha256=A8rvegBF2nD6etbBRb8hsv-oZyjpD4VcUQ8PffW-bqU,992
37
+ gam/gamlib/glverlibs.py,sha256=xoQXiwcE_-HVYKv-VYA8O0mazRsc9mN-_ysj1dAlMyc,992
38
38
  gam/gamlib/yubikey.py,sha256=-UC-3oue9qarYK3LT7YL6Gmqs7TMK8oz9_AoxdaG2FA,7925
39
39
  gam/gdata/__init__.py,sha256=uvjmSza2EdL7lGaoJ04-uXHGeYa0i1dBQHIetBybcUQ,29637
40
40
  gam/gdata/service.py,sha256=CuImJDRVcNMM1dfo7V6T0LrztzqTNrIraaLkHXpL0Tw,70045
@@ -65,8 +65,8 @@ gam/googleapiclient/discovery_cache/base.py,sha256=yCDPtxnbNN-p5_9fzBacC6P3wcUPl
65
65
  gam/googleapiclient/discovery_cache/file_cache.py,sha256=sim3Mg4HgRYo3vX75jvcKy_aV568EvIrtBfvfbw-044,4774
66
66
  gam/iso8601/__init__.py,sha256=Z2PsYbXgAH5a5xzUvgczCboPzqWpm65kRcIngCnhViU,1218
67
67
  gam/iso8601/iso8601.py,sha256=Li2FHZ4sBTWuthuQhyCvmvj0j6At8JbGzkSv2fc2RHU,4384
68
- gam7-7.9.1.dist-info/METADATA,sha256=bBb1CBudSxPlVO_9OsXduoRE_D8RJs0MHCgIQUOt8b4,2977
69
- gam7-7.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
- gam7-7.9.1.dist-info/entry_points.txt,sha256=HVUM5J7dA8YwvJfG30jiLefR19ExMs387TWugWd9sf4,42
71
- gam7-7.9.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
- gam7-7.9.1.dist-info/RECORD,,
68
+ gam7-7.9.3.dist-info/METADATA,sha256=-pArRqB1q188WZNbLnAXpdWANZdOmRGonVeFc2H_eC0,2977
69
+ gam7-7.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
+ gam7-7.9.3.dist-info/entry_points.txt,sha256=HVUM5J7dA8YwvJfG30jiLefR19ExMs387TWugWd9sf4,42
71
+ gam7-7.9.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
+ gam7-7.9.3.dist-info/RECORD,,
File without changes